根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址

Windows动态获取函数地址技术解析与应用 动态链接库(DLL)是Windows系统模块化设计的核心组件,通过导出表机制实现函数动态加载。在二进制安全领域,动态获取函数地址技术通过解析PE文件结构和进程环境块(PEB),绕过静态导入实现API调用,是Shellcode编写和漏洞利用的关键基础。该技术涉及内存地址计算、导出表遍历等底层操作,能有效规避安全软件的静态检测。在逆向工程、恶意代码分析等场景中,掌握动态获取LoadLibraryA/GetProcAddress等核心API的方法,对理解Windows内存管理和攻防对抗原理具有重要意义。 阅读详情

本节与PE文件格式相关的术语以<<微软PE/COFF规范>>为准([13]),不再强调该点。
winnt.h中定义了部分相关数据结构,后面如未单独注明来自哪个头文件,均隐指来
自winnt.h。执行vulnerable_0.exe,进入windbg调试状态。

> !list -t _LIST_ENTRY.Flink -x "dd" -a "+18 L1" 241ec0
00241ed8  00400000
00241f30  77f50000
00241fd8  77e60000
... ...

从上节可知,ntdll.dll基址是0x77f50000,kernel32.dll基址是0x77e60000。最开
始的64字节按如下数据结构解析:

--------------------------------------------------------------------------
#define IMAGE_DOS_SIGNATURE 0x5A4D      // MZ

typedef struct _IMAGE_DOS_HEADER        // DOS .EXE header
{
    WORD   e_magic;                     // +0x00 Magic number
    WORD   e_cblp;                      // +0x02 Bytes on last page of file
    WORD   e_cp;                        // +0x04 Pages in file
    WORD   e_crlc;                      // +0x06 Relocations
    WORD   e_cparhdr;                   // +0x08 Size of header in paragraphs
    WORD   e_minalloc;                  // +0x0a Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // +0x0c Maximum extra paragraphs needed
    WORD   e_ss;                        // +0x0e Initial (relative) SS value
    WORD   e_sp;                        // +0x10 Initial SP value
    WORD   e_csum;                      // +0x12 Checksum
    WORD   e_ip;                        // +0x14 Initial IP value
    WORD   e_cs;                        // +0x16 Initial (relative) CS value
    WORD   e_lfarlc;                    // +0x18 File address of relocation table
    WORD   e_ovno;                      // +0x1a Overlay number
    WORD   e_res[4];                    // +0x1c Reserved words
    WORD   e_oemid;                     // +0x24 OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // +0x26 OEM information; e_oemid specific
    WORD   e_res2[10];                  // +0x28 Reserved words
    LONG   e_lfanew;                    // +0x3c File address of new exe header
                                        // +0x40
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
--------------------------------------------------------------------------

> db 77e60000 L0n64
77e60000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
77e60010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
77e60020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
77e60030  00 00 00 00 00 00 00 00-00 00 00 00 f8 00 00 00  ................
> dd 77e60000+3c L1 (显示e_lfanew成员)
77e6003c  000000f8

其中e_lfanew成员用于定位PE头。从注释中理解,e_lfanew是File pointer,非RVA,
不过在这里当成RVA处理也没关系。e_lfanew值为0x000000f8,PE头在基址加0xf8的
位置。PE头最开始是标识"PE/0/0",占4字节,然后是20字节固定头。

--------------------------------------------------------------------------
#define IMAGE_NT_SIGNATURE       0x00004550  // PE00
#define IMAGE_SIZEOF_FILE_HEADER 20

typedef struct _IMAGE_FILE_HEADER
{
    WORD    Machine;               // +0x00
    WORD    NumberOfSections;      // +0x02
    DWORD   TimeDateStamp;         // +0x04
    DWORD   PointerToSymbolTable;  // +0x08
    DWORD   NumberOfSymbols;       // +0x0c
    WORD    SizeOfOptionalHeader;  // +0x10
    WORD    Characteristics;       // +0x12
                                   // +0x14
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
--------------------------------------------------------------------------

> db 77e60000+f8 L0n24
77e600f8  50 45 00 00 4c 01 04 00-28 fa 6d 3d 00 00 00 00  PE..L...(.m=....
77e60108  00 00 00 00 e0 00 0e 21
                      ^^^^^
接下来是可选头,SizeOfOptionalHeader成员表明可选头占用了224字节(0x00e0)。

--------------------------------------------------------------------------
typedef struct _IMAGE_OPTIONAL_HEADER
{
    WORD    Magic;                        // +0x00
    BYTE    MajorLinkerVersion;           // +0x02
    BYTE    MinorLinkerVersion;           // +0x03
    DWORD   SizeOfCode;                   // +0x04
    DWORD   SizeOfInitializedData;        // +0x08
    DWORD   SizeOfUninitializedData;      // +0x0c
    DWORD   AddressOfEntryPoint;          // +0x10
    DWORD   BaseOfCode;                   // +0x14
    DWORD   BaseOfData;                   // +0x18
    DWORD   ImageBase;                    // +0x1c
    DWORD   SectionAlignment;             // +0x20
    DWORD   FileAlignment;                // +0x24
    WORD    MajorOperatingSystemVersion;  // +0x28
    WORD    MinorOperatingSystemVersion;  // +0x2a
    WORD    MajorImageVersion;            // +0x2c
    WORD    MinorImageVersion;            // +0x2e
    WORD    MajorSubsystemVersion;        // +0x30
    WORD    MinorSubsystemVersion;        // +0x32
    DWORD   Win32VersionValue;            // +0x34
    DWORD   SizeOfImage;                  // +0x38
    DWORD   SizeOfHeaders;                // +0x3c
    DWORD   CheckSum;                     // +0x40
    WORD    Subsystem;                    // +0x44
    WORD    DllCharacteristics;           // +0x46
    DWORD   SizeOfStackReserve;           // +0x48
    DWORD   SizeOfStackCommit;            // +0x4c
    DWORD   SizeOfHeapReserve;            // +0x50
    DWORD   SizeOfHeapCommit;             // +0x54
    DWORD   LoaderFlags;                  // +0x58
    DWORD   NumberOfRvaAndSizes;          // +0x5c Number of data-dictionary entries in the remainder of the Optional Header
                                          // +0x60
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
                                          // +0xe0
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY
{
    DWORD   VirtualAddress;  // +0x00 RVA
    DWORD   Size;            // +0x04 The size in bytes
                             // +0x08
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES     16

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
--------------------------------------------------------------------------

IMAGE_DATA_DIRECTORY.VirtualAddress是RVA。一般情况DataDirectory[]是含有16
个元素的结构数组。前两个元素分别对应Export Directory与Import Directory。但
是规范3.4.3小节指出元素个数不固定,需要检查NumberOfRvaAndSizes成员确定元素
个数。

此外,不要假设IMAGE_DATA_DIRECTORY.VirtualAddress指向所在section的起始位置,
比如Import Directory一般位于.idata section中,但不能假设RVA指向.idata的起
始位置。不要假设Import Directory所在section一定拥有".idata"这个名字。

> db 77e60000+f8+0n24 L0n224
77e60110  0b 01 07 00 00 56 07 00-00 dc 06 00 00 00 00 00  .....V..........
77e60120  60 ae 01 00 00 10 00 00-00 20 07 00 00 00 e6 77  `........ .....w
77e60130  00 10 00 00 00 02 00 00-05 00 01 00 05 00 01 00  ................
77e60140  04 00 00 00 00 00 00 00-00 60 0e 00 00 04 00 00  .........`......
77e60150  d3 7e 0e 00 03 00 00 00-00 00 04 00 00 10 00 00  .~..............
77e60160  00 00 10 00 00 10 00 00-00 00 00 00 10 00 00 00  ................
77e60170  40 d0 06 00 39 6b 00 00-7c 3b 07 00 28 00 00 00  @...9k..|;..(...
77e60180  00 a0 07 00 d8 5e 06 00-00 00 00 00 00 00 00 00  .....^..........
77e60190  00 00 00 00 00 00 00 00-00 00 0e 00 54 53 00 00  ............TS..
77e601a0  fc 63 07 00 38 00 00 00-00 00 00 00 00 00 00 00  .c..8...........
77e601b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
77e601c0  a8 76 07 00 40 00 00 00-90 02 00 00 1c 00 00 00  .v..@...........
77e601d0  00 10 00 00 0c 06 00 00-00 00 00 00 00 00 00 00  ................
77e601e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
> dd 77e60000+f8+0n24+5c L1 (检查NumberOfRvaAndSizes成员)
77e6016c  00000010
> dd 77e60000+f8+0n24+60 L2 (定位Export Directory,当前基是16)
77e60170  0006d040 00006b39
          ^^^^^^^^
> ? 77e60000+0006d040+00006b39
Evaluate expression: 2012035961 = 77ed3b79
> ? 77e60000+0006d040
Evaluate expression: 2012008512 = 77ecd040

现在我们知道Export Directory在"77e60000+0006d040",格式如下:

--------------------------------------------------------------------------
typedef struct _IMAGE_EXPORT_DIRECTORY
{
    DWORD   Characteristics;        // +0x00
    DWORD   TimeDateStamp;          // +0x04
    WORD    MajorVersion;           // +0x08
    WORD    MinorVersion;           // +0x0a
    DWORD   Name;                   // +0x0c Name of the DLL
    DWORD   Base;                   // +0x10 Starting ordinal number for exports
    DWORD   NumberOfFunctions;      // +0x14 Number of entries in the EAT
    DWORD   NumberOfNames;          // +0x18 Number of entries in the ENPT/EOT
    DWORD   AddressOfFunctions;     // +0x1c RVA from base of image
    DWORD   AddressOfNames;         // +0x20 RVA from base of image
    DWORD   AddressOfNameOrdinals;  // +0x24 RVA from base of image
                                    // +0x28
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
--------------------------------------------------------------------------

AddressOfFunctions

    指向Export Address Table。

    DWORD EAT[NumberOfFunctions];

EAT[i]

    可选头中IMAGE_DATA_DIRECTORY结构决定了引出信息范围。如果EAT[i]的值不在
    引出信息范围,则为函数地址(Export RVA)。否则,该值指向形如dllname.#27
    或者dllname.exportfunc的ASCIZ串,用MS术语说,这是一个Forwarder RVA,表
    示由另外一个dll实际引出某函数。

AddressOfNames

    指向Export Name Pointer Table

    DWORD ENPT[NumberOfNames];

ENPT[i]

    指向Export Name Table中某个位置

    ENT由ASCIZ串构成

AddressOfNameOrdinals

    指向Export Ordinal Table

    WORD EOT[NumberOfNames];

参看规范6.3.4小节,这几个数组之间的关系用伪C语言描述如下:

--------------------------------------------------------------------------
/*
* 怀疑该规范与MS最终实现有出入,规范中EOT[0]应该等于ordinal_base的,但MS
* 最终实现里EOT[0]等于0。这里的伪代码符合MS的最终实现。看来,理论->实践->
* 再理论的学习过程永远都要牢记,否则死菜了都不知道怎么死菜的。
*/
index         = Search_ENPT( function_name );
index         = EOT[ index ];
function_addr = EAT[ index ];
ordinal       = ordinal_base + index;
--------------------------------------------------------------------------

为何多出个EOT,因为可能不同的函数名对应同一个函数地址,参[14]的12.5小节。

假设源代码中有如下声明:

__declspec(dllexport) int __stdcall PublicFunc ( int a, int b, int c, int d );

假设没有使用DEF文件:

EXPORTS

    PublicFunc

同时却又在源代码出现:

#pragma comment( linker, "/EXPORT:PublicFunc=_PublicFunc@32" )

此时生成PE文件时会同时引出两个符号,"PublicFunc"与"_PublicFunc@32",这两个
符号对应同一个函数。反映在上述几个数组中,即EOT[i]等于EOT[j]。

NumberOfFunctions与NumberOfNames在这种情形下不等。除此之外,还有一种不等情
形。NumberOfNames为0,NumberOfFunctions不为0,表示模块仅通过ordinal引出函
数,这是相当极端却有可能出现的情形。

VC有个现成的工具dumpbin,可用于观察引出(export)信息:

> dumpbin X:/XP/system32/kernel32.dll /exports

  Section contains the following exports for KERNEL32.dll

    00000000 characteristics
    3D6DE616 time date stamp Thu Aug 29 17:15:02 2002
        0.00 version
           1 ordinal base
         942 number of functions
         942 number of names

    ordinal hint RVA      name

          1    0 000137E8 ActivateActCtx
          2    1 000093FE AddAtomA
          3    2 0000D496 AddAtomW
          4    3 000607C5 AddConsoleAliasA
          5    4 0006078E AddConsoleAliasW
          6    5 0004E0A1 AddLocalAlternateComputerNameA
          7    6 0004DF8C AddLocalAlternateComputerNameW
          8    7 00035098 AddRefActCtx
          9    8          AddVectoredExceptionHandler (forwarded to NTDLL.RtlAddVectoredExceptionHandler)
         10    9 00036909 AllocConsole
        ... ...
        401  190 0001B332 GetProcAddress
        ... ...
        571  23A 0001D961 LoadLibraryA
        572  23B 0001D941 LoadLibraryExA
        573  23C 0001D839 LoadLibraryExW
        574  23D 00013B38 LoadLibraryW
        ... ...

回windbg验证一下:

> db 77e60000+0006d040 L28
77ecd040  00 00 00 00 16 e6 6d 3d-00 00 00 00 34 f5 06 00  ......m=....4...
77ecd050  01 00 00 00 ae 03 00 00-ae 03 00 00 68 d0 06 00  ............h...
77ecd060  20 df 06 00 d8 ed 06 00
> da 77e60000+poi(0x77e60000+0x0006d040+0xc) (Name)
77ecf534  "KERNEL32.dll"
> dd 0x77e60000+0x0006d040+0x10 L1 (Base)
77ecd050  00000001
> dd 0x77e60000+0x0006d040+0x14 L1 (NumberOfFunctions)
77ecd054  000003ae
> dd 0x77e60000+0x0006d040+0x18 L1 (NumberOfNames)
77ecd058  000003ae
> dd 0x77e60000+0x0006d040+0x1c L3 (AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals)
77ecd05c  0006d068 0006df20 0006edd8

套用这个公式:

--------------------------------------------------------------------------
0x190      = Search_ENPT( "GetProcAddress" );
0x190      = EOT[ 0x190 ];
0x77e7b332 = EAT[ 0x190 ];
0x191      = 1 + 0x190;
--------------------------------------------------------------------------

> da 77e60000+poi(77e60000+0006df20+0x190*4) (访问ENPT、ENT)
77ed1393  "GetProcAddress"
> dw 77e60000+0006edd8+0x190*2 L1 (访问EOT)
77ecf0f8  0190
> ? 77e60000+poi(77e60000+0006d068+0x190*4) (访问EAT)
Evaluate expression: 2011673394 = 77e7b332
> u 77e7b332 (这个地址不在[77ecd040, 77ed3b79)内)
kernel32!GetProcAddress:
77e7b332 55               push    ebp
77e7b333 8bec             mov     ebp,esp
77e7b335 51               push    ecx
77e7b336 51               push    ecx
77e7b337 53               push    ebx
77e7b338 57               push    edi
77e7b339 8b7d0c           mov     edi,[ebp+0xc]
77e7b33c bbffff0000       mov     ebx,0xffff

再来验证一下Forwarder RVA的情形:

--------------------------------------------------------------------------
8          = Search_ENPT( "AddVectoredExceptionHandler" );
8          = EOT[ 8 ];
0x77ed38ad = EAT[ 8 ];
9          = 1 + 8;
--------------------------------------------------------------------------

> da 77e60000+poi(77e60000+0006df20+8*4) (访问ENPT、ENT)
77ecf5cf  "AddVectoredExceptionHandler"
> dw 77e60000+0006edd8+8*2 L1 (访问EOT)
77ecede8  0008
> ? 77e60000+poi(77e60000+0006d068+8*4) (访问EAT)
Evaluate expression: 2012035245 = 77ed38ad
> da 77e60000+poi(77e60000+0006d068+8*4) (这个地址在[77ecd040, 77ed3b79)内)
77ed38ad  "NTDLL.RtlAddVectoredExceptionHan"
77ed38cd  "dler"

现在总结一下"根据PE文件格式获取LoadLibraryA()/GetProcAddress()地址"全过程:

a. 通过TEB/PEB获取kernel32.dll基址

b. 在(基址+0x3c)处获取e_lfanew

c. 在(基址+e_lfanew+0x78)处获取Export Directory地址(后面为描述方便简称export)

d. 在(基址+export+0x1c)处获取AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals

e. 搜索ENPT,确定"LoadLibraryA"、"GetProcAddress"所对应的index

f. index = EOT[ index ];

g. function_addr = EAT[ index ];

下面是完整的C语言演示程序,汇编化留到编写完整shellcode时进行。

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/EWindows XP SP1 & VC 7
*         : cl GetAddr.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*         :
* Create  : 2003-08-14 15:11
* Modify  :
* -----------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

static void * __stdcall SearchAPI ( char *BASE, DWORD *EAT, DWORD *ENPT, WORD *EOT, DWORD num, char *name )
{
    DWORD  index         = 0;
    char  *function_name = NULL;
    void  *function_addr = NULL;

    for ( index = 0; index < num; index++ )
    {
        function_name = ENPT[index] + BASE;
        /*
         * 大小写敏感比较
         */
        if ( 0 == strcmp( function_name, name ) )
        {
            index         = EOT[index];
            function_addr = EAT[index] + BASE;
            return( function_addr );
        }
    }  /* end of for */
    return( NULL );
}  /* end of SearchAPI */

int __cdecl main ( int argc, char * argv[] )
{
    void  *PEB                   = NULL,
          *Ldr                   = NULL,
          *Flink                 = NULL,
          *kernel32_BaseAddress  = NULL,
          *kernel32_BaseDllName  = NULL,
          *ExportDirectory       = NULL,
          *PrivateLoadLibraryA   = NULL,
          *PrivateGetProcAddress = NULL;
    DWORD  NumberOfNames         = 0,
          *AddressOfFunctions    = NULL,
          *AddressOfNames        = NULL;
    WORD  *AddressOfNameOrdinals = NULL;
    LONG   e_lfanew              = 0;

    __asm
    {
        mov     eax,fs:[0x30]
        mov     PEB,eax
    }
    printf( "PEB                   = 0x%08X/n", PEB );
    Ldr                   = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
    printf( "Ldr                   = 0x%08X/n", Ldr );
    Flink                 = *( ( void ** )( ( unsigned char * )Ldr + 0x1c ) );
    printf( "Flink                 = 0x%08X/n", Flink );
    Flink                 = *( ( void ** )Flink );
    kernel32_BaseAddress  = *( ( void ** )( ( unsigned char * )Flink + 0x08 ) );
    kernel32_BaseDllName  = *( ( void ** )( ( unsigned char * )Flink + 0x20 ) );
    printf( "kernel32_BaseAddress  = 0x%08X/n", kernel32_BaseAddress );
    wprintf( L"kernel32_BaseDllName  = %s/n", kernel32_BaseDllName );
    /*
     * 根据PE文件格式进行解析
     */
    e_lfanew              = *( ( LONG * )( ( unsigned char * )kernel32_BaseAddress + 0x3c ) );
    printf( "e_lfanew              = 0x%08X/n", e_lfanew );
    ExportDirectory       = *( ( DWORD * )( ( unsigned char * )kernel32_BaseAddress + e_lfanew + 0x78 ) ) +
                            ( unsigned char * )kernel32_BaseAddress;
    printf( "ExportDirectory       = 0x%08X/n", ExportDirectory );
    NumberOfNames         = *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x18 ) );
    printf( "NumberOfNames         = %u/n", NumberOfNames );
    AddressOfFunctions    = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x1c ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfFunctions    = 0x%08X/n", AddressOfFunctions );
    AddressOfNames        = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x20 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNames        = 0x%08X/n", AddressOfNames );
    AddressOfNameOrdinals = ( WORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x24 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNameOrdinals = 0x%08X/n", AddressOfNameOrdinals );
    PrivateLoadLibraryA   = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                "LoadLibraryA"
                            );
    printf( "PrivateLoadLibraryA   = 0x%08X/n", PrivateLoadLibraryA );
    printf( "LoadLibraryA          = 0x%08X/n", LoadLibraryA );
    PrivateGetProcAddress = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                "GetProcAddress"
                            );
    printf( "PrivateGetProcAddress = 0x%08X/n", PrivateGetProcAddress );
    printf( "GetProcAddress        = 0x%08X/n", GetProcAddress );
    return( EXIT_SUCCESS );
}  /* end of main */

#if 0

/*
* 按初始化顺序前向遍历链表,第二个节点对应kernel32.dll。
*/

#include <stdio.h>
#include <stdlib.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

int __cdecl main ( int argc, char * argv[] )
{
    void *PEB         = NULL,
         *Ldr         = NULL,
         *Flink       = NULL,
         *p           , = NULL,
         *BaseAddress = NULL,
         *BaseDllName = NULL;

    __asm
    {
        mov     eax,fs:[0x30]
        mov     PEB,eax
    }
    printf( "PEB   = 0x%08X/n", PEB );
    Ldr   = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
    printf( "Ldr   = 0x%08X/n", Ldr );
    Flink = *( ( void ** )( ( unsigned char * )Ldr + 0x1c ) );
    printf( "Flink = 0x%08X/n", Flink );
    p     = Flink;
    do
    {
        BaseAddress = *( ( void ** )( ( unsigned char * )p + 0x08 ) );
        BaseDllName = *( ( void ** )( ( unsigned char * )p + 0x20 ) );
        printf( "p     = 0x%08X 0x%08X ", p, BaseAddress );
        wprintf( L"%s/n", BaseDllName );
        p = *( ( void ** )p );
    }
    while ( Flink != p );
    return( EXIT_SUCCESS );
}  /* end of main */

#endif
--------------------------------------------------------------------------

执行效果如下,对比PrivateLoadLibraryA与LoadLibraryA的值,应该相等。

> GetAddr
PEB                   = 0x7FFDF000
Ldr                   = 0x00241E90
Flink                 = 0x00241F28
kernel32_BaseAddress  = 0x77E60000
kernel32_BaseDllName  = kernel32.dll
e_lfanew              = 0x000000F8
ExportDirectory       = 0x77ECD040
NumberOfNames         = 942
AddressOfFunctions    = 0x77ECD068
AddressOfNames        = 0x77ECDF20
AddressOfNameOrdinals = 0x77ECEDD8
PrivateLoadLibraryA   = 0x77E7D961
LoadLibraryA          = 0x77E7D961
PrivateGetProcAddress = 0x77E7B332
GetProcAddress        = 0x77E7B332

这里演示的技巧不只用于exploit、shellcode、virus,还大量用于特殊Driver编程
中。根据PE文件格式解析内存中的模块映像是很基础的知识,我今天才接触了一下,
惭愧。

SearchAPI()用到了被搜索函数名,直接在shellcode中保存完整函数名会占用不少空
间。可以考虑在shellcode中保存函数名的某种哈希值,同时SearchAPI()比较哈希值
而非函数名。任何一种哈希算法都会丢失部分原有信息,以致不同函数名产生的哈希
值相同,所谓"碰撞"。virus编程中为此常常使用标准CRC32算法([8]),参29A-4.227。
但是LSD认为CRC32的汇编算法太长了([15]),他们用了一个相当简单的算法:

while ( *c )
{
    h = ( ( h << 5 ) | ( h >> 27 ) ) + *c++;
}

不超过10行汇编代码。据LSD的报告称,对超过5000个不同的dll测试,覆盖50000个
不同的函数名,该哈希算法未产生一次碰撞。若真是如此,对于编写shellcode来讲,
完全足够了。

下面是完整的C语言演示程序,真正汇编化后应提前计算函数名的哈希值。

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/EWindows XP SP1 & VC 7
*         : cl GetAddr_0.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*         :
* Create  : 2003-08-14 17:24
* Modify  :
* -----------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

static DWORD __stdcall GetHash ( char *c )
{
    DWORD h = 0;

    while ( *c )
    {
        h = ( ( h << 5 ) | ( h >> 27 ) ) + *c++;
    }
    return( h );
}  /* end of GetHash */

static void * __stdcall SearchAPI ( char *BASE, DWORD *EAT, DWORD *ENPT, WORD *EOT, DWORD num, DWORD NameHash )
{
    DWORD  index         = 0,
           h             = 0;
    char  *function_name = NULL;
    void  *function_addr = NULL;

    for ( index = 0; index < num; index++ )
    {
        function_name = ENPT[index] + BASE;
        h             = GetHash( function_name );
        if ( h == NameHash )
        {
            index         = EOT[index];
            function_addr = EAT[index] + BASE;
            return( function_addr );
        }
    }  /* end of for */
    return( NULL );
}  /* end of SearchAPI */

int __cdecl main ( int argc, char * argv[] )
{
    void  *PEB                   = NULL,
          *Ldr                   = NULL,
          *Flink                 = NULL,
          *kernel32_BaseAddress  = NULL,
          *kernel32_BaseDllName  = NULL,
          *ExportDirectory       = NULL,
          *PrivateLoadLibraryA   = NULL,
          *PrivateGetProcAddress = NULL;
    DWORD  NumberOfNames         = 0,
          *AddressOfFunctions    = NULL,
          *AddressOfNames        = NULL,
           NameHash              = 0;
    WORD  *AddressOfNameOrdinals = NULL;
    LONG   e_lfanew              = 0;

    __asm
    {
        mov     eax,fs:[0x30]
        mov     PEB,eax
    }
    printf( "PEB                   = 0x%08X/n", PEB );
    Ldr                   = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
    printf( "Ldr                   = 0x%08X/n", Ldr );
    Flink                 = *( ( void ** )( ( unsigned char * )Ldr + 0x1c ) );
    printf( "Flink                 = 0x%08X/n", Flink );
    Flink                 = *( ( void ** )Flink );
    kernel32_BaseAddress  = *( ( void ** )( ( unsigned char * )Flink + 0x08 ) );
    kernel32_BaseDllName  = *( ( void ** )( ( unsigned char * )Flink + 0x20 ) );
    printf( "kernel32_BaseAddress  = 0x%08X/n", kernel32_BaseAddress );
    wprintf( L"kernel32_BaseDllName  = %s/n", kernel32_BaseDllName );
    /*
     * 根据PE文件格式进行解析
     */
    e_lfanew              = *( ( LONG * )( ( unsigned char * )kernel32_BaseAddress + 0x3c ) );
    printf( "e_lfanew              = 0x%08X/n", e_lfanew );
    ExportDirectory       = *( ( DWORD * )( ( unsigned char * )kernel32_BaseAddress + e_lfanew + 0x78 ) ) +
                            ( unsigned char * )kernel32_BaseAddress;
    printf( "ExportDirectory       = 0x%08X/n", ExportDirectory );
    NumberOfNames         = *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x18 ) );
    printf( "NumberOfNames         = %u/n", NumberOfNames );
    AddressOfFunctions    = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x1c ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfFunctions    = 0x%08X/n", AddressOfFunctions );
    AddressOfNames        = ( DWORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x20 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNames        = 0x%08X/n", AddressOfNames );
    AddressOfNameOrdinals = ( WORD * )
                            (
                                *( ( DWORD * )( ( unsigned char * )ExportDirectory + 0x24 ) ) +
                                ( unsigned char * )kernel32_BaseAddress
                            );
    printf( "AddressOfNameOrdinals = 0x%08X/n", AddressOfNameOrdinals );
    NameHash              = GetHash( "LoadLibraryA" );
    printf( "NameHash              = 0x%08X/n", NameHash );
    PrivateLoadLibraryA   = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                NameHash
                            );
    printf( "PrivateLoadLibraryA   = 0x%08X/n", PrivateLoadLibraryA );
    printf( "LoadLibraryA          = 0x%08X/n", LoadLibraryA );
    NameHash              = GetHash( "GetProcAddress" );
    printf( "NameHash              = 0x%08X/n", NameHash );
    PrivateGetProcAddress = SearchAPI
                            (
                                kernel32_BaseAddress,
                                AddressOfFunctions,
                                AddressOfNames,
                                AddressOfNameOrdinals,
                                NumberOfNames,
                                NameHash
                            );
    printf( "PrivateGetProcAddress = 0x%08X/n", PrivateGetProcAddress );
    printf( "GetProcAddress        = 0x%08X/n", GetProcAddress );
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

> GetAddr_0
PEB                   = 0x7FFDF000
Ldr                   = 0x00241E90
Flink                 = 0x00241F28
kernel32_BaseAddress  = 0x77E60000
kernel32_BaseDllName  = kernel32.dll
e_lfanew              = 0x000000F8
ExportDirectory       = 0x77ECD040
NumberOfNames         = 942
AddressOfFunctions    = 0x77ECD068
AddressOfNames        = 0x77ECDF20
AddressOfNameOrdinals = 0x77ECEDD8
NameHash              = 0x331ADDDC <- "LoadLibraryA"的哈希值
PrivateLoadLibraryA   = 0x77E7D961
LoadLibraryA          = 0x77E7D961
NameHash              = 0x99C95590 <- "GetProcAddress"的哈希值
PrivateGetProcAddress = 0x77E7B332
GetProcAddress        = 0x77E7B332

☆ 参考资源

[13] Microsoft Portable Executable and Common Object File Format Specification
     http://www.microsoft.com/whdc/hwdev/download/hardware/pecoff.doc
     http://www.microsoft.com/whdc/hwdev/download/hardware/pecoff.pdf

[14] <<Programming Applications for Microsoft Windows, Fourth Edition>> - Jeffrey Richter

[15] http://www.lsd-pl.net/documents/winasm-1.0.1.pdf
     http://www.lsd-pl.net/documents/winasm.ppt
     http://lsd-pl.net/projects/winasm-1.0.1.tar.gz
 

第10章 shellcode实战:06 原则:函数地址的动态获取 实现对GetProcAdress函数地址的获取后,就可以结合kernel32的基址,再次获取到LoadLibraryA(或LoadLibraryW)这个函数的地址,然后就可以LoadLibraryA+GetProcAdress实现对任意函数的动态调用。上述GetProcAddress地址的获取,是基于PE结构导出表定位函数地址的原理。上一讲我们介绍了kernel32基址的获取,这一节来介绍如何解决GetProcAddress动态调用的问题。在上述代码中,我们测试了MessageBoxA函数的动态调用。 阅读详情

相关推荐

字符串替换—牛客网(C++实现)

一.题目描述 请你实现一个简单的字符串替换函数。原串中需要替换的占位符为"%s",请按照参数列表的顺序一一替换占位符。若参数列表的字符数大于占位符个数。则将剩下的参数字符添加到字符串的结尾。 给定一个字符串A,同时给定它的长度n及参数字符数组arg,请返回替换后的字符串。保证参数个数大于等于占位符个数。保证原串由大小写英文字母组成,同时长度小于等于500。 测试样例: “A%sC%sE”,7,[‘...

qq_38358582的博客 1523

LoadLibraryExA函数说明

Qt源码解析 索引 LoadLibraryExA函数说明 语法 HMODULE LoadLibraryExA( [in] LPCSTR lpLibFileName, HANDLE hFile, [in] DWORD dwFlags ); 参数 [in] lpLibFileName 。参考LoadLibrary hFile 此参数保留为将来使用. It must be NULL. [in] dwFlags 加载模块时要执行的操作。如果未指定任何标志,则此函数的行...

xinqingwuji的博客 2297

基于STM32的智能循迹小车设计(基础版)

基于STM32的智能循迹小车设计 硬件准备 1、小车底盘+四直流电机(带轮) 2、STM32F103C8T6核心板 3、12V 8700mAh锂电池(可以用几节18650锂电池) 4、循迹模块 选择的是TCRT500L,五路集成在一起,集成度比较高,但是可能不如五个单独的循迹模块好用 5、L298N电机驱动模块 四驱车为啥用L298N电机驱动模块? 可以把左边的两个电机并联,用输出A控制。把右边的两个电机并联,用输出B控制。 12V供电接电源正极,供电GND接电源负极和核心板GND,5V供电接核心

Rendezvous1的博客 3万+

通用ShellCode学习笔记——win7中获得LoadLibraryA地址

一、ShellCode的编写 Kernel32地址的获取 由于win7的_PEB_LDR_DATA表和以前的系统有了改变,直接查询0x08方式在win7下失效,为了保证shellcode的通用性(主要是增加对win7的支持),采用遍历查询的方式定位kernel32的位置 esi=fs:0->TEB esi=TEB:30h->PEB esi=PEB:0ch->_P...

weixin_30548917的博客 495

手把手教你用Cursor + 自定义MCP工具链,让AI帮你搞定App逆向(附adb-mcp/frida-mcp源码)

本文详细介绍了如何利用Cursor IDE与自定义MCP工具链构建AI驱动的App逆向工作流,涵盖adb-mcp和frida-mcp的源码实现与实战应用。通过自然语言指令,AI可自动化完成设备控制、动态Hook和数据分析,显著提升移动安全测试效率。适合安全研究人员和开发者快速掌握智能逆向技术。

weixin_33737134的博客 1303

Windows下用Anaconda安装d2l库避坑指南(附PermissionError解决方案)

本文详细介绍了在Windows系统下使用Anaconda安装d2l库的完整指南,特别针对常见的PermissionError问题提供了多种解决方案。从环境准备到库安装,再到VSCode配置和数据路径处理,帮助读者顺利搭建深度学习环境并运行《动手学深度学习》的代码示例。

3a9bq4r8t2y的博客 226

解决 LoadLibraryExA 、 LoadLibrary 参数错误 GetLastError()值87

require "LuaDebugger" 在一台机器上正确,但在另一台机器上却错误,跟踪到底是LoadLibraryExA 87号错误,参数不正确。 百思不得其解,一开始还以为是UNICODE的问题,后来发现不是,百度半天,终于发现解决方案,修改dll工程链接选项,禁止增量链接就行了。 这是vc的bug吧,我是vs2010。

AmoyCHC的专栏 4781

移植 lwip,在 windows 内核构建一个 TCPIP 协议栈

移植 lwip,在 windows 内核构建一个 TCPIP 协议栈 亲,当你点开看这篇文章,就说明你知道我想要干什么事情,或者说,你知道在 Windows 的内核种绕开它的网络协议,再独立构建一个网络协议栈的意义。如果你要问我有 windows 系统的网络协议栈不用,移植 lwip 有什么意义,那么你可以关闭链接走人了,因为这篇文章不适合你,它不是讲 lwip 移植原理的。 经常关注我的博客的朋...

oRbIt 的专栏 3578

typedef函数代码段解释以及部分Windows下的系统函数

typedef函数代码段解释以及部分Windows下的系统函数

森明帮大于黑虎帮的博客 1405

【全部更新】2025金地杯E题山西省大学生数学建模思路代码文章教学:高速铁路牵引供电系统能耗优化

问题四将列车的运行速度与发车时间同时作为优化变量,建立联合优化模型,目标是在考虑电价机制和调度均衡性的前提下,使牵引供电系统一天内的总电费支出最小。问题三在考虑分时电价机制的基础上,基于遗传算法优化列车速度组合,使牵引供电系统一天内的总电费支出最小。然而,在实际运营中,不同时段的电价差异显著,合理安排列车的运行时间与运行速度,可有效降低系统的总电费支出。本研究聚焦于高速铁路牵引供电系统的能耗与电费优化问题,针对列车运行速度、发车时间与分时电价的综合影响,提出一系列数学模型,以实现系统能耗与电费的最小化。

xb459149247的博客 5289

【编译原理】LL(1)分析方法 生成语法树(c++实现)

基本流程 输入文法->消除左递归->提取左公共因子->求FIRST集->求FOLLOW集->LL(1)分析表->表驱动的预测语法分析过程->输出语法分析结果和语法树

qq_43417265的博客 1万+

PE文件病毒实验(二)——实验报告

实验目的:掌握PE文件格式;理解病毒感染PE文件的原理。 实验内容: (1) 了解PE文件格式。 (2) 根据实验步骤,编程实现在PE文件中插入病毒代码。运行插入病毒代码后的PE文件。 (3) 编程实现在磁盘中搜索.exe文件的操作,对于所有的.exe文件插入病毒代码并运行插入病毒代码后的exe文件。 (4) 编程实现在磁盘中搜索(3)中的.exe文件的操作,对于所有的.exe文件删除病毒代码并运...

jmhIcoding 1万+

虚幻商城 蓝图汇总

虚幻引擎是一个非常强大和灵活的游戏开发工具,拥有众多的蓝图可以帮助我们更高效地工作。

一碗清深的博客 1508

根据PE文件格式获取LoadLibraryA()

本节与PE文件格式相关的术语以>为准([13]),不再强调该点。winnt.h中定义了部分相关数据结构,后面如未单独注明来自哪个头文件,均隐指来自winnt.h。执行vulnerable_0.exe,进入windbg调试状态。> !list -t _LIST_ENTRY.Flink -x "dd" -a "+18 L1" 241ec000241ed8  0040000000241f30  77f5

1855

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

PE学习3-动态获取Kernel32的基址,动态搜索API地址

;------------------------ ;AUTHOR:leeleehom DATE:09/04/2010 20:12 ;SearchKernel32Base.asm ;功能描述:动态搜索KERNEL32.DLL的基址,动态搜索API地址 ;------------------------ .386 .model flat, stdcall option casemap:none ;------------------------ include windows.inc ;-

WIN32汇编学习园地 1322

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

《Windows PE权威指南》学习之第13章 PE补丁技术(2)

第12章介绍了PE变形技术,该技术研究的是程序的字节码;本章来研究PE补丁技术,该技术侧重于研究使用Masm32编写的补丁程序,而非程序字节码。PE补丁技术被广泛应用于PE病毒、PE加密解密等领域,通过对目标程序嵌入不同的补丁程序,可以实现不同的目的。 PE补丁分为动态补丁和静态补丁,其中静态补丁框架由两部分组成:补丁程序和将补丁程序附加到目标PE的补丁工具。

chenyijun的专栏 936

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

非调用手法获取API

使用非调用手法获取API,其实是PE病毒的基本手段之一。搜索输出表,导出GetModuleHandle,LoadLibraryA,GetProcAddress函数的地址,然后用LoadLibraryA调用USER32.DLL,然后再使用GetProcAddress得到MessageBoxA()的函数,得到输出框。GetModuleHandle暂时没用到。。。汗一个。 .386.mode

zhkza99c的专栏 997

暴力搜索内存空间获得 Api 的线性地址

 暴力搜索内存空间获得 Api 的线性地址暴力?怕怕……呵呵,其实这里的“暴力”只是一个形象的比喻。首先说明,本文也是老掉牙的东东了,如果你已经懂得在内存中搜索 Api 的技术,那就不要浪费时间在这篇文章上了。但如果你还是一名初学者,那么看看本文,应该还是有点帮助的。首先我们来看看为什么要在内存中搜索 Api 的线性地址。我们知道,一个 PE 文件在编译和连接成功后,会有一个 import tab

bbdg的专栏 1374

PE可执行文件的镶入式程序后门开发

/*       利用异常结构处理搜索GetProcAddress入口地址   */    #include      #include      main()    {        _asm        {            call ex//取得当前地址以计算异常结构开始的地址             mov     eax,0x77000000            mov    

天涯专栏 872

eac 反调试_白帽Wiki - 白帽入门,从这里开始!

用来干什么懂的都懂```cpp#define HUOJI_POOL_TAG 'huoJ'#define CALCSIZE(n,f) (ULONG_PTR)f - (ULONG_PTR)n#define PAGE_ALIGN(Va) ((PVOID)((ULONG_PTR)(Va) & ~(PAGE_SIZE - 1)))typedef HMODULE(WINAPI* LOADLIBRARY...

weixin_35064596的博客 500

《Windows PE权威指南》学习之第22章 PE病毒提示器

本文介绍了两种PE病毒提示器的开发方法:半自动手工补丁和全自动补丁框架。通过分析PE病毒感染机制,选择系统记事本程序(notepad.exe)作为宿主文件,在PE文件头4Ch偏移处存储节表校验和。当程序运行时,通过比较当前校验和与原始值来判断是否被病毒感染。提示器通过注册表启动项实现开机自启,并复制到系统目录增加感染几率。测试表明,修改节表内容会触发病毒警告提示。虽然该工具无法阻止病毒入侵,但能有效提醒用户系统安全状态,并可通过扩展实现自我修复功能。

chenyijun的专栏 931

缓冲溢出Shellcode 代码

汇编程序:main() { __asm __emit 0x90 __asm __emit 0x90 __asm { mov ebp, esp push ebp ;获取kernel32.dll的基址 保存于eax pu

jackydai987的专栏 940
上一篇: PCShrink 0.71 部分源代码(带Packer)
下一篇: 我的PE程序加密核心代码(MASM 6.0
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值