• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1211
  • Last Modified:

Password Filter

Hi all,

I've put numerous queries out about this, but no-one has managed to provide a concise answer.  Our company are migrating from NetWare to Active Directory, and part of that project involves writing a DLL which will check the new passwords to make sure they meet set criteria : at least 8 chars, one of which being numeric. However, there is scant documentation on this apart from here : http://www.devx.com/security/Article/21522/0/page/3

However, he doesn't tell you how to test the password, which involves messing around with function prototypes.

As it stands, my DLL is loading, but and i can get the procedure address, but it wont run. Could someone please give me some information on this topic?

Regards

Pushpop





-------------------------------------------------------------------------------------------------------------------
DLL code:


///////////////////////////////////////////////////////////////
//Filename:Password.cpp                                                             //
//                                                                                           //
//Authors: DevX.com, Eric Chubb (PWC,GTS,WILTON PLACE)             //
//                                                                                           //
//Last modified: 10/05/06                                                       //
//                                                                                           //
//Purpose: DLL for LSASS.exe which acts as a                         //
//password filter for AD logon                                                 //
//                                                                                           //
//Criteria for acceptable passwords include minimum 8 chars  //
//with at least one of those being a numeric character             //
///////////////////////////////////////////////////////////////

#include "stdafx.h"                                    //requisite header files
#include <stdio.h>
#include <windows.h>
#include <ntsecapi.h>
#include <comdef.h>
#include <string>
#include <time.h>
#include <wchar.h>
#define _WRITE_LOGS 1
#define LOGFILE "c:\\FilterLog.txt"    // log destination
using namespace std;


// Default DllMain implementation
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                               )
{
      switch (ul_reason_for_call)  //optional startup checks
      {
            case DLL_PROCESS_ATTACH:
            
            case DLL_THREAD_ATTACH:
            
            case DLL_THREAD_DETACH:
            
            case DLL_PROCESS_DETACH:
            
            break;
      }
    return TRUE;
}

// This is a helper function to write log lines to file
// only applies if LSASS.exe defines _WRITE_LOGS


void WriteToLog(const char* str)
{
      #ifdef _WRITE_LOGS
      if (NULL == str)
      {
            return;
      }
      FILE* log;
      log = fopen(LOGFILE, "w");
      if (NULL == log)
      {
            return;
      }
      fprintf(log, "%s\r\n", str);
      fclose(log);
#endif

      return;
}

/////////////////////////////////////////////
// Exported function                                 //
// -----------------                                 //
// Initialization of Password filter.         //
// This implementation just returns TRUE   //
// to let LSA know everything is fine         //
/////////////////////////////////////////////

extern "C" __declspec(dllexport)BOOLEAN InitializeChangeNotify(void)
{
      
      return TRUE;
}

/////////////////////////////////////////////////
// Exported function                                       //
// -----------------                                       //
// This function is called by LSA when password//
// was successfully changed.                           //
//                                                                     //
// This implementation just returns 0 (Success)//
/////////////////////////////////////////////////

extern "C" __declspec(dllexport)NTSTATUS  PasswordChangeNotify(
  PUNICODE_STRING UserName,
  ULONG RelativeId,
  PUNICODE_STRING NewPassword
)
{
      WriteToLog("PasswordChangeNotify()");
      return 0;
}
////////////////////////////////////////////
// Exported function                                //
// -----------------                                //
// this function actually validates       //
// a new password.                        //
//                                        //
// Returns True if accepted               //
////////////////////////////////////////////

// apparently this function type *HAS* to have this signature or else
// LSASS will be unable to call the function........
 
                                       
 extern "C" __declspec(dllexport)BOOLEAN PasswordFilter(    //these are the variables which will be passed in
  PUNICODE_STRING AccountName,             //by LSASS.exe on runtime      
  PUNICODE_STRING FullName,                   //your guess is as good as mine on what
  PUNICODE_STRING Password,          //exactly a PUNICODE_STRING is
  BOOLEAN SetOperation
)
{
      WriteToLog("Entering PasswordFilter()");
      size_t count = 0;
      wstring wstrPassword;      
      wchar_t* wszPassword = NULL;
      bool bMatch = FALSE;                  //if both testOne and testTwo are true, bMatch is too
      bool testOne = FALSE;                  //both of these test conditions must
      bool testTwo = FALSE;                  //be satisfied for the password to be
      
      try                                                //accepted
      {
            
            wszPassword = new wchar_t[Password->Length + 1];
            if (NULL == wszPassword)
            {
                  throw E_OUTOFMEMORY;
            }
            wcsncpy(wszPassword, Password->Buffer, Password->Length);
            wszPassword[Password->Length] = 0;

            WriteToLog("Going to check password");
            wstrPassword = wszPassword;

            // Validate password against GUID criteria
            WriteToLog("checking password");
            if (wcslen(wszPassword) < 8) // if password shorter than 8 chars
            {
                  testOne = false;
            }
            else
                  testOne = true;
            
            while(count < wcslen(wszPassword))
            {
                  if(iswdigit(wszPassword[count]))  //if there is at least one digit*/
                  {
                        testTwo = true;
                                             // our criteria have been satisfied
                  }
                  count++;
            }

            if(testOne == true && testTwo ==true)
            {
                  bMatch = true;
            }
            else
                  bMatch = false;

            if (bMatch)                        /*we're in business*/
            {
                  WriteToLog("Password satisfies criteria");
                  MessageBox(NULL,"Congratulations, your password is accepted!","Password Accepted",NULL);
            }
            else            // try again
            {
                  MessageBox(NULL,"Sorry, but you're password is not acceptable","Password Error",NULL);
                  WriteToLog("Password does NOT match specified GUID criteria");
            }
            throw S_OK;
      }
      catch(HRESULT)
      {
            WriteToLog("error filtering password");
      }
      catch(...)
      {
            //WriteToLog("error filtering password");

      }
      // Erase all temporary password data
      // for security reasons
      wstrPassword.replace(0, wstrPassword.length(), wstrPassword.length(), (wchar_t)'?');
      wstrPassword.erase();
      
      
      if (NULL != wszPassword)
      {
            ZeroMemory(wszPassword, Password->Length);

            // Assure that there is no compiler optimizations and read random byte
            // from cleaned password string
            srand(time(NULL));
            wchar_t wch = wszPassword[rand() % Password->Length];
            delete [] wszPassword;
            wszPassword = NULL;
      }
      return bMatch;
}

//EOF


---------------------------------------------------------------------------------------------------------------------


test program code:



/*program which loads a DLL
 *and gets the address of a
 *given function within the
 *it
 */


#include <stdio.h>
#include <windows.h>

HMODULE hDLL;
typedef int (*DLLPROC)(LPTSTR);
BOOL b = FALSE;
DLLPROC PasswordFilter;
DWORD dw;
LPVOID lpMsgBuf;


int main()
{
      lpMsgBuf = (char *)malloc(400);
      if((hDLL = LoadLibrary("Password"))!=NULL)
      {
            printf("DLL loaded correctly\n");
            
      }
      else{
            printf("DLL didn't load properly\n");
            exit(0);
      }
      PasswordFilter = (DLLPROC) GetProcAddress(hDLL, "PasswordFilter");
   
      if(PasswordFilter==NULL)
      {
      printf("function PasswordFilter didn't load correctly: ");       
            
      }

      else if(PasswordFilter!=NULL)
      {
      printf("The password filter procedure loaded correctly");       
            
      }
      
/*      dw = GetLastError();       
       FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );
      

*/

      PasswordFilter("hi","hi","hi",b);
            
      return 0;
}









0
pushpop
Asked:
pushpop
  • 3
  • 3
  • 3
2 Solutions
 
fridomCommented:
Why are you using this crude mixture between C++ and C? Are you looking for an C answer please post compilable C code, if you want C++ I'd suggest using the C++ chanel.

And I can not see what you mean with "does not run".
Why do you generate a new copy from the given parameter Password. You handle it with all kind of stuff but I can not understand why a copy is needed.

I dropped also all the parameter but the Password (this is the thing you like to check ot do I get that wrong?)

I can not see that NULL is a BOOLEAN value so I fixed that also:
So I came up with the following code for the DLL:

#include <stdio.h>
#include <wchar.h>
#include <ctype.h>
#include <windows.h>


enum {
      REQUIRED_LENGTH=8};


BOOL APIENTRY LibMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                          )
{
     switch (ul_reason_for_call)  //optional startup checks
     {
          case DLL_PROCESS_ATTACH:

          case DLL_THREAD_ATTACH:

          case DLL_THREAD_DETACH:

          case DLL_PROCESS_DETACH:

          break;
     }
    return TRUE;
}




__declspec(dllexport)BOOL PasswordFilter(wchar_t * Password)    //these are the variables which will be passed in
      {
      size_t count = 0;
      BOOL pass = FALSE;
      size_t len = 0;
      /* does the Password get through our conditions,
      go the safe way anser NO as default */


      if (wcslen(Password) > REQUIRED_LENGTH){ // if password shorter than 8 chars{
            pass = TRUE;
      }
      if (! pass) {
            /* test not satisfied give up */
            return pass;
      }
    /* we have passed the first test
         no let's try the second test */
      pass = FALSE;
      len = wcslen(Password);
      while((count < len) && !pass) {
            if(iswdigit(Password[count])){
                  pass = TRUE;
            }
            count++;
      }
      return pass;
}

With the following test file:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

typedef BOOL (*PWFilter) (wchar_t * );


int main(void){
      char *dll_name= "lcc\\pw_checker.dll";
      HMODULE hDLL;
      PWFilter PasswordFilter;
      wchar_t * valid_pw = L"ein123akadak";
      wchar_t * invalid_pw = L"ein";

     if((hDLL = LoadLibrary(dll_name))!=NULL)
     {
          printf("DLL loaded correctly\n");

     }
     else{
          printf("DLL didn't load properly\n");
          exit(EXIT_FAILURE);
     }
     PasswordFilter = (PWFilter) GetProcAddress(hDLL, "_PasswordFilter");

     if(PasswordFilter == NULL){
            printf("function PasswordFilter didn't load correctly: ");
       } else {
           printf("The password filter procedure loaded correctly");

     }
       /* use the function */
       printf("PasswordFilter(%S) = %d\n", valid_pw, PasswordFilter(valid_pw));
        printf("PasswordFilter(%S) = %d\n", invalid_pw, PasswordFilter(invalid_pw));
       FreeLibrary(hDLL);
       return 0;

}

I get all the expected things:
DLL loaded correctly
The password filter procedure loaded correctly
PasswordFilter(ein123akadak) = 1
PasswordFilter(ein) = 0

Run on a Windows Server 2003 with lcc-win32.

So I guess you have to clean up your code a bit and see if it works then.

Regards
Friedrich

0
 
cwwkieCommented:
@fridom,
this was asked in c++ (http:Q_21872311.html), but received no answer so far...

@pushpop,
you should have posted a pointer (see http://www.experts-exchange.com/help.jsp#hi262) instead. That way all suggestions will end up in one thread.
0
 
pushpopAuthor Commented:
Crude the mixture of code may be, but the fact is that most of it isn't mine. The DLL code is based on code I saw on a website. The reason why PasswordFilter takes 4 parameters and not 1 is that LSASS.exe process which loads the DLL passes four parameters in to the function on invocation. It's dawning on me that the code wasn't the best to begin with, but there is scant documentation on how to implement this sort of thing otherwise.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
cwwkieCommented:
The most important is to get the example (unmodified) working. Someone else got that working, so you must be able to do the same.

In your test program, you are using char pointers for the parameters:
     PasswordFilter("hi","hi","hi",b);
But you have to use the correct function prototype in your test program. Something like:

    typedef BOOLEAN (*PWFilter) (PUNICODE_STRING,PUNICODE_STRING,PUNICODE_STRING,BOOLEAN );

Initialise the PUNICODE_STRING using a function like RtlInitUnicodeString
0
 
pushpopAuthor Commented:
Fridom: Thanks for that code, it works a treat! Again, I must re-iterate that 75% of it wasn't my own and also, I am a student who got off to a bad start with C (boring, indcipherable lecturer!.

cwwkie: Thank's for your contribution, I did what you said but when I compile it I get the following:

Error E2238 test.c 5: Multiple declaration for 'PUNICODE_STRING'
Error E2238 test.c 5: Multiple declaration for 'PUNICODE_STRING'

That's not a typo, the compiler produces the same message twice. Using Borland 5.5

What am I doing wrong?  :-)
0
 
cwwkieCommented:
You have to include the header where PUNICODE_STRING is defined. wdm.h or ntddk.h according to the following page:
http://msdn.microsoft.com/library/en-us/Kernel_r/hh/Kernel_r/k112_9f862aaa-4cd6-4420-8255-ad577d8a8c59.xml.asp
0
 
fridomCommented:
Well you *can* pick up code if you found it that's quite ok. But to apply it you have to understand it. As I can see you question has at least a few edges:
- you have no idea about the used datatypes.
- and you want this code to be plugged into windows somewhere

You approach trying to solve it all at once is doomed to fail. Therefor I stepped back and showed you how to solve one part of it. My code can be used to build the shared library and it shows how to use it. So this part is done, and your question was about why your code doesn't work.

Now the next part it to plug it into windows. But that is another question.

Regards
Friedrich
0
 
pushpopAuthor Commented:
Ok,

This is the new version of my DLL. Hopefully it will work!


#include <stdio.h>
#include <wchar.h>
#include <ctype.h>
#include <windows.h>
#include <string>
using namespace std;


typedef struct _UNICODE_STRING {
  USHORT  Length;
  USHORT  MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;




enum {
     REQUIRED_LENGTH=8};


BOOL APIENTRY LibMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                          )
{
     switch (ul_reason_for_call)  //optional startup checks
     {
          case DLL_PROCESS_ATTACH:

          case DLL_THREAD_ATTACH:

          case DLL_THREAD_DETACH:

          case DLL_PROCESS_DETACH:

          break;
     }
    return TRUE;
}

__declspec(dllexport)BOOLEAN InitializeChangeNotify(void)
{
     
     return TRUE;
}


extern "C"__declspec(dllexport) PasswordChangeNotify(
  PUNICODE_STRING UserName,
  ULONG RelativeId,
  PUNICODE_STRING NewPassword
)
{

     return 0;
}



extern "C"__declspec(dllexport)BOOL PasswordFilter(PUNICODE_STRING AccountName,           //by LSASS.exe on runtime    
  PUNICODE_STRING FullName,                //your guess is as good as mine on what
  PUNICODE_STRING Password,          //exactly a PUNICODE_STRING is
  BOOLEAN SetOperation
)    //these are the variables which will be passed in
     {
     size_t count = 0;
     BOOL pass = FALSE;
     size_t len = 0;
       wstring wstrPassword;    
     wchar_t* wszPassword = NULL;
     
       /* does the Password get through our conditions,
     go the safe way anser NO as default */
      wszPassword = new wchar_t[Password->Length + 1];
   
      
      if (NULL == wszPassword)
    {
         throw E_OUTOFMEMORY;
    }
          wcsncpy(wszPassword, Password->Buffer, Password->Length);
          wszPassword[Password->Length] = 0;

         
          wstrPassword = wszPassword;

          // Validate password against GUID criteria
       

      
     if (wcslen(wszPassword) >= REQUIRED_LENGTH){ // if password shorter than 8 chars{
          pass = TRUE;
     }
     if (! pass) {
          /* test not satisfied give up */
          return pass;
     }
    /* we have passed the first test
        no let's try the second test */
     pass = FALSE;
     len = wcslen(wszPassword);
     while((count < len) && !pass) {
          if(iswdigit(wszPassword[count])){
               pass = TRUE;
          }
          count++;
     }
     return pass;
}
0
 
fridomCommented:
I can not see why you do make the copy. And well you missed to free the allocated memory. So at least you have a leak in your code, if you compile that code as C++, I wonder how LibMain will get mangeled.

Friedrich
0

Featured Post

Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

  • 3
  • 3
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now