Click here to Skip to main content
Click here to Skip to main content
Go to top

Login password filters in WinXP

, 25 May 2006
Rate this:
Please Sign up or sign in to vote.
An article on how to build login password filters on WinXP.

Sample Image

Introduction

In this article, I will demonstrate how to increase the security at the Windows login screen by creating a password filter. This allows an organization to have more strict requirements about minimum password complexity. Although someone could also create additional functionality, such as a GUI interface to set up specifics of what the minimum complexity should be, I will mostly focus on the very basics needed to get the filter working.

Background

To get an idea of where this information can be found in its purest form, you can look in MSDN Library | Security| Security (General) | Management | Using Management | Using Password Filters. Also, sample programs are installed in the following directory of VS.NET 2003 (student version): Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\samples\Security\NetProviders. Both of these have information that is needed to get a project to work, but I have seen that neither of these are sufficient information by themselves, and need to be merged.

Using the Code

To begin with, we are going to make a DLL that exports certain functions that the operating system will call to activate. Also, we will insert entries into the registry that will let the OS know what password filter we are using. And finally, we will have to to activate the password filter using the Local Security Policies MMC plug-in. So, let's begin with creating the DLL first.

Building the DLL

  1. The first thing you will need to do is to create a new Visual C++ project: select Win32 folder, click Win32 Project, and enter a project name.
  2. In the next screen, you will need to select Application Settings and select DLL project. If you want to export symbols, that is optional. It can make stub functions if you have never done this, it can help you understand what DLLs really do.
  3. Now specifically, there are at least three functions that are a minimum set of functions that must be exported. This is done by making a .def file and the minimum set of exported functions for the password filter:
    • LIBRARY LoginFilter
    • EXPORTS
    • NPGetCaps
    • NPLogonNotify
    • NPPasswordChangeNotify
  4. Now that the compiler knows what needs to be exported, it's time to look at the function signatures. The following header files need to be included for compilation:
    • #include <Npapi.h>
    • #include <Ntsecapi.h>
    • WORD WINAPI NPGetCaps ( DWORD nIndex )
    • DWORD WINAPI NPLogonNotify ( PLUID lpLogonId, LPCWSTR lpAuthentInfoType, LPVOID lpAuthentInfo, LPCWSTR lpPreviousAuthentInfoType, LPVOID lpPreviousAuthentInfo, LPWSTR lpStationName, LPVOID StationHandle, LPWSTR *lpLogonScript )
    • DWORD WINAPI NPPasswordChangeNotify ( LPCWSTR lpAuthentInfoType, LPVOID lpAuthentInfo, LPCWSTR lpPreviousAuthentInfoType, LPVOID lpPreviousAuthentInfo, LPWSTR lpStationName, LPVOID StationHandle, DWORD dwChangeInfo )

    These by themselves do nothing without any implementation. Since the goal of this is to obtain the user's password to see if it meets a certain standard, we need to get the password from the parameters that are coming into these functions. In this particular example, the user name and password are not checked with any pattern. This example just shows where the information is stored.

    // LoginFilter.cpp : Defines the entry point for the DLL application. //
    #include "stdafx.h" 
    #include <Npapi.h>
    #include <Ntsecapi.h>
    #define MSV1_0_AUTH_TYPE L"MSV1_0:Interactive" 
    #define KERBEROS_TYPE L"Kerberos:Interactive" 
    #define LOGFILE TEXT("C:\\Login.txt") 
    BOOL WriteLogFile(LPTSTR String); 
    BOOL APIENTRY DllMain( HANDLE hModule, 
         DWORD ul_reason_for_call, LPVOID lpReserved ) 
    {
        if (ul_reason_for_call == DLL_PROCESS_ATTACH) 
        {
            DisableThreadLibraryCalls((HMODULE)hModule); 
        }
        return TRUE;
    }
    DWORD WINAPI NPGetCaps( DWORD nIndex ) 
    {
        DWORD dwRes; 
        switch (nIndex) 
        {
            case WNNC_NET_TYPE: 
                dwRes = WNNC_CRED_MANAGER; // credential manager 
                break;
            case WNNC_SPEC_VERSION:
                // We are using version 5.1 of the spec. 
                dwRes = WNNC_SPEC_VERSION51;
                break;
            case WNNC_DRIVER_VERSION: 
                dwRes = 1; // This driver is version 1. 
                break; 
            case WNNC_START:
                dwRes = 1; // We are already "started" 
                break;
            default:
                dwRes = 0; // We don't support anything else 
                break;
        }
        return dwRes; 
    }
    
    DWORD WINAPI NPLogonNotify ( PLUID lpLogonId, 
          LPCWSTR lpAuthentInfoType, LPVOID lpAuthentInfo, 
          LPCWSTR lpPreviousAuthentInfoType, 
          LPVOID lpPreviousAuthentInfo, 
          LPWSTR lpStationName, LPVOID StationHandle, 
          LPWSTR *lpLogonScript ) 
    {
        PMSV1_0_INTERACTIVE_LOGON pAuthInfo; 
        TCHAR szBuf[1024]; 
        //Be careful of the TEMPLATE escape sequences, 
        //in this case I used = %lS to force UNICODE 
        //otherwise it would have to have been defined.
        char *FormateInfo = "StationName=%lS DomainName" 
                 " = %lS UserName=%lS Password=%lS\r\n"; 
        
        // // If the primary authenticator is not MSV1_0, return success.
        // Why? Because this is the only auth info structure that we 
        // understand and we don't want to interact with other types. // 
        if ( lstrcmpiW (MSV1_0_AUTH_TYPE, lpAuthentInfoType) ) 
        {
            //Any sort of file IO can take place here but mostly just to 
            //let the user know that we are not 
            //intrested in this data stucter type.
            SetLastError(NO_ERROR);
            return NO_ERROR; 
        } 
        // // Do something with the authentication information 
        // This is the data structure we really need!
        // The information is stored as UNICODE strings.
    
        //pAuthInfo->LogonDomainName.Buffer
        //pAuthInfo->Password.Buffer
        //pAuthInfo->UserName.Buffer
        
        pAuthInfo = (PMSV1_0_INTERACTIVE_LOGON) lpAuthentInfo; 
        if(pAuthInfo->LogonDomainName.Length>0) 
        {
            if(pAuthInfo->Password.Length>0)
            { 
                if(pAuthInfo->UserName.Length>0)
                {
                    wsprintf(szBuf, FormateInfo, lpStationName, 
                      pAuthInfo->LogonDomainName.Buffer, 
                      pAuthInfo->UserName.Buffer, 
                      pAuthInfo->Password.Buffer); 
                    MessageBox(NULL, szBuf,"Login Info",MB_OK); 
                    WriteLogFile(szBuf); 
                }
                else 
                    MessageBox(NULL,"No Username","",MB_OK);
            }
            else 
                MessageBox(NULL,"No Password","",MB_OK);
        }
        else
            MessageBox(NULL,"No domain Name","",MB_OK);
        // Let's utilize the logon script capability to display 
        // our logon information 
        // 
        // The Caller MUST free this memory 
        *lpLogonScript = (LPWSTR)LocalAlloc(LPTR,1024); 
        wsprintf(*lpLogonScript,L"notepad %s",LOGFILE); 
    
        return NO_ERROR; 
    }
    
    DWORD WINAPI NPPasswordChangeNotify ( LPCWSTR lpAuthentInfoType, 
          LPVOID lpAuthentInfo, LPCWSTR lpPreviousAuthentInfoType, 
          LPVOID lpPreviousAuthentInfo, LPWSTR lpStationName, 
          LPVOID StationHandle, DWORD dwChangeInfo ) 
    {
        //Same information about parameters are found NPLogonNotify
        return NO_ERROR;
    }
    
    BOOL WriteLogFile(LPTSTR String) 
    { 
        HANDLE hFile;
        DWORD dwBytesWritten; 
        hFile = CreateFile( LOGFILE, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 
                FILE_FLAG_SEQUENTIAL_SCAN, NULL ); 
        if (hFile == INVALID_HANDLE_VALUE)
        return FALSE; 
        //
        // Seek to the end of the file 
        // 
        SetFilePointer(hFile, 0, NULL, FILE_END); 
        WriteFile( hFile, String, lstrlen(String)*sizeof(TCHAR), 
                                        &dwBytesWritten, NULL ); 
        CloseHandle(hFile);
        return TRUE;
    }
  5. Now that the function has some very primitive implementation, the DLL can be built.
    • (Side note) Once built, a DLL typically can't run by itself, it will need the login screen to actually run the code. This is problematic for debugging since certain fatal errors may prevent you from logging on to your own station. If you are unsure about the code you write, try testing it out in a regular executable to keep from crashing at login.
    • You should also place the DLL in the C:\Windows\System32 folder. This is in accordance with the Microsoft documentation.
    • You can also add two more function signatures to your EXPORTS if you want to make the DLL completely contained. This will be explained in the next step as well. STDAPI DllRegisterServer(void) and STDAPI DllUnregisterServer(void) can be implemented to add the necessary registry entries.

Adding to the Registry

OK, now that the DLL is made, it is time to add the registry entries in the right spots. This is where the documentation provided in MSDN was fragmented.

  1. In the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\, append a line to Notification Packages with the name of your DLL minus the extension. So, for example: PasswordFilter.dll becomes PasswordFilter. Do not overwrite the entries currently there!!!! The entries are all on separate lines and are of the type REG_MULTI_SZ.
  2. In HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NetworkProvider, append the name of your DLL minus the extension in ProviderOrder. These entries are separated by a comma. This entry is of the type REG_SZ.
  3. Now, create a new key in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\. So, it looks like this: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LoginFilter.
  4. In this new key, add a subkey called NetworkProvider. It should look like this: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ Services\LoginFilter\NetworkProvider.
  5. In this subkey, add the following registry names and types:
    Class REG_DWORD 2
    Name REG_SZ Login Filter
    ProviderPath REG_EXPAND_SZ %SystemRoot%\system32\LoginFilter.dll
  6. Wow, what a mess! It seems like a lot to be typed in, so this is where DllRegisterServer and DllUnregisterServer come in. Adding implementation to these can package all the information that is needed about your DLL in the DLL. Using registry functions, you can manipulate the registry automatically. Once you have implemented those two functions, you would use Regsvr32.exe to register the password filter.

Starting the Password Filter

  1. Assuming that the compiled DLL is in the C:\WINDOWS\SYSTEM32 directory and all the registry entries have been entered correctly, the only thing left to do is to turn on the service that activates it.
  2. Go to Start -> Control Panel -> Performance and Maintenance (Category View) -> Administrator Tools -> Local Security Policies.
  3. From the root, Security Settings | Account Polices | Password Polices. Double click on the Password must meet complexity requirements.
  4. Enable this setting.
  5. Log out, and the password filter policy will take effect.

Stopping the Password Filter

  1. Go to Start -> Control Panel -> Performance and Maintenance (Category View)-> Administrator Tools ->Local Security Policies.
  2. From the root, Security Settings | Account Polices| Password Polices. Double click on the Password must meet complexity requirements.
  3. Disable this setting.
  4. Reboot.
  5. Delete DLL out of C:\WINDOWS\SYSTEM32. The password filter may still be active after all this, I believe it may be cached.

Points of Interest

One landmine I discovered is that wsprintf has TEMPLATE escape characters. If UNICODE is defined, it assumes that the incoming parameter is UNICODE; if it is not, it assumes it to be ANSI. Also, wsprintf has escape characters to force the modes, so just keep in mind what mode you want. Since the buffers in the data structure (pAuthInfo->UserName.Buffer) are UNICODE based, if UNICODE is not defined, ordinary string functions will not work. Normal string functions will presume UNICODE characters as being empty strings. Be sure to use UNICODE string functions to solve this. Although I took out most of the code that actually filters the password, this was mainly done to focus on the interface and not the details of the filter.

Since you are working with the security in this way, you should use any best practice known to keep passwords secure. Even consider using obfuscation techniques to prevent attackers from learning too much about your algorithms.

I can not emphasis enough how this could be misused. Once misused, this becomes a password grabber.

While testing, I noticed that disabling the password filter from the Local Security Polices MMC plug-in wasn't enough. It still remains active even after reboot. I have deleted the filter DLL after the setting was disabled, and rebooted to keep the login filter from being active afterwards. Not sure why this happens yet; if anyone finds out why I, am curious to know.

History

This is the first release. This is no more then a template program. More work will be done on self registration, defining settings, and filtering, later.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

icestatue
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralAgainst my will Pinmembericestatue18-May-06 7:21 
GeneralAnother critic Pinmemberlegolas.dev17-May-06 1:06 
GeneralRe: Another critic Pinmembericestatue17-May-06 2:47 
GeneralRe: Another critic Pinmemberlegolas.dev17-May-06 3:37 
GeneralWhich part is Yours here? it is a sample from SDK Pinmemberlegolas.dev17-May-06 0:48 
GeneralRe: Which part is Yours here? it is a sample from SDK Pinmembericestatue17-May-06 2:34 
GeneralRe: Which part is Yours here? it is a sample from SDK Pinmemberlegolas.dev17-May-06 2:48 
GeneralNot working properly Pinmembernimishsudan17-Mar-06 14:28 
GeneralRe: Not working properly Pinmembericestatue20-Mar-06 2:37 
I probably should have rewritten this article without mixing forms of UNICODE and ANSI style functions. And without testing this idea at current time because of my current work load I will give some pointers to the right direction for now and later I will fix the article. Basically UNICODE was created to allow more types of characters to display then standard english characters. The way this was done was this. If you view a normal ANSI encoded string in note pad you might see a string like this "Hello", However, UNICODE functions interleave an extra byte between them so a UNICODE string appears as such "H e l l o". This kind of string would stop most ANSI string functions prematurally since they usually stop at a null terminated string. So in order to Solve this problem UNICODE versions of string manipulation functions had to be created. And since no one likes to differentiate between the 2 versions Microsoft made these functions conditional MACROS that are based on certain preprossesor directives. Which in our case is #define UNICODE, internally you will see that wsprintf is actually wsprintfW in the case of a UNICODE and wsprintfA in the case of ANSI. And since you should probably be using UNICODE when interacting with Network Providers you should probably stick to these functions. At the time I wrote this I was trying a very crude way of converting the UNICODE information to an ANSI form so I could easily monitor files without the e x t r a s p a c e s !
 
P.S. Hope this helps a bit. You can usally determine the UNICODE and ANSI versions of the function by right clicking the function name and selection go to definition this will take you to the conditional statment for its definition;)
 
nothing
 
-- modified at 7:45 Tuesday 25th April, 2006
GeneralGood stuff! PinmemberIvo Ivanov21-Dec-05 15:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 25 May 2006
Article Copyright 2005 by icestatue
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid