Skip to content →

动态定位 Win32 API 的入口地址

shellcode是一段能够完成某种特定功能的二进制代码,可能是开启一个新的shell或者下载某个特定的程序或者向攻击者返回一个shell等等。在Windows中,完成这些特定功能需要调用某些API,其入口地址可由链接库的装载基地址和API在链接库中的偏移相加得到。早些时候,可以这样来获取API的入口地址:进入VC6.0安装目录下的Tools文件夹,其中Depends.exe工具可以用来查看PE文件用到的所有链接库的装载基址,以及每个链接库的所有导出函数及偏移地址。

但是,操作系统版本号和补丁版本号开始影响动态链接库的加载地址,而且不同版本的链接库中API的偏移量也不尽相同,因此上述方法计算得到的API入口地址无法在各种操作系统版本上通用。

借助LoadLibraryA()和GetProcAddress()可以实现API入口地址的动态定位。

我们知道,所有win32程序都会加载ntdll.dll和kernel32.dll这两个最基础的动态链接库,而LoadLibraryA()和GetProcAddress()恰好就在kernel32.dll的导出表中,只要能获得这两个API的入口地址,就可以加载其他dll文件并获取其中的API。

具体定位过程:

  1. 通过段寄存器FS在内存中找到当前的线程环境块TEB。
  2. 线程环境块偏移0x30存放着指向进程环境块PEB的指针。
  3. 进程环境块偏移0x0C存放着指向PEB_LDR_DATA结构体的指针,其中存放着进程装载动态链接库的基本信息。
  4. PEB_LDR_DATA结构体偏移0x1C存放着模块初始化链表的头指针InInitializationOrderModuleList。
  5. 模块初始化链表InInitializationOrderModuleList按照模块的初始化顺序排列,每个结点都指向一个模块。
  6. 找到某个模块对应的结点后,在其基础上再偏移0x08就是该模块在内存中的基地址。
  7. 模块基地址偏移0x3C是该模块的PE头。
  8. PE头偏移0x78存放着指向函数导出表的指针。导出表偏移0x1C处的指针指向地址列表,列表中存储了导出函数的偏移地址;导出表偏移0x20处的指针指向名称列表,列表中存储了导出函数的函数名。地址列表与名称列表中的元素一一对应。
  9. 在名称列表中定位到所需API是第几个,然后在地址列表中找到相应的RVA,将RVA与之前获得的模块基地址相加,就是所需API在内存中的虚拟地址。

下面的shellcode就使用了动态定位API的方法,功能是执行MessageBoxA(0, “4321”, “4321”, 0)弹出窗口,最后执行ExitProcess(0)安全退出。代码下载地址:popMessageBox.cpp

/*
In Visual Studio:
Project/Property/Linker/Advanced/Data Execution Prevention(DEP) -> NO
*/

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

void popMessageBox() {
    __asm {
        jmp     start

find_function:
        push    ebp
        mov     ebp,esp

        mov     eax,fs:[0x30]           // get the pointer to PEB
        mov     eax,[eax+0x0c]          // get PEB->ldr
        mov     eax,[eax+0x14]          // get PEB->ldr.InMemoryOrderModuleList.Flink

module_loop:
        mov     eax,[eax]               // skip the 1st entry, or get the next entry
        mov     esi,[eax+0x28]          // LDR_MODULE->BaseDllName
        cmp     byte ptr [esi+0x0c],'3' // judge if the 7th char of the module is '3'
        jne     module_loop
                                        // find kernel32.dll module
        mov     eax,[eax+0x10]          // LDR_MODULE->BaseAddress

        mov     edi,eax
        add     edi,[edi+0x3c]          // IMAGE_DOS_HEADER->e_lfanew: the PE Header

        mov     edi,[edi+0x78]          // IMAGE_NT_HEADERS->OptinalHeader.DataDirectory[0].VirtualAddress
        add     edi,eax                 // the address of Export Table
        mov     ebx,edi

        mov     edi,[ebx+0x20]          // IMAGE_EXPORT_DESCRIPTOR->AddressOfNames
        add     edi,eax                 // the address of Names
        xor     ecx,ecx                 // NameOrdinals
  
name_loop:
        mov     esi,[edi+ecx*4]
        add     esi,eax                 // the address of Name[ecx]
        inc     ecx
        mov     edx,[esp+8]             // The first half of the parameter
        cmp     dword ptr [esi],edx
        jne     name_loop
        mov     edx,[esp+0xc]           // The second half of the parameter
        cmp     dword ptr [esi+4],edx
        jne     name_loop

        mov     edi,[ebx+0x24]          // IMAGE_EXPORT_DESCRIPTOR->AddressOfNameOrdinals
        add     edi,eax                 // the address of NameOrdinals
        mov     ecx,[edi+ecx*2]         // NameOrdinals[ecx]
        and     ecx,0xFFFF              // just use its lower 16-bits

        mov     edi,[ebx+0x1c]          // IMAGE_EXPORT_DESCRIPTOR->AddressOfFunctions
        add     edi,eax                 // the address of Functions
        dec     ecx
        sal     ecx,2
        mov     edi,[edi+ecx]           // Functions[ecx]
        add     eax,edi                 // the address of the function we need

        pop     ebp
        ret

start:
        push    0x41636f72              // "rocA"
        push    0x50746547              // "GetP"
        call    find_function           // Get GetProcAddress's address, __cdecl
        add     esp,8                   // pop "GetProcA", clear parameters we push in stack

        push    eax                     // store GetProcAddress() in stack

        push    0x7262694c              // "Libr"
        push    0x64616f4c              // "Load"
        call    find_function           // Get LoadLibraryA's address, __cdecl
        add     esp,8                   // pop "LoadLibr"

        push    eax                     // store LoadLibraryA() in stack

        push    0x3233                  // "32"
        push    0x72657375              // "user"
        push    esp                     // lpFileName="user32"
        call    eax                     // LoadLibraryA("user32.dll"), __stdcall
        add     esp,8                   // pop "user32"

        push    0x41786f                // "oxA"
        push    0x42656761              // "ageB"
        push    0x7373654d              // "Mess"
        push    esp                     // lpProcName="MessageBoxA"
        push    eax                     // hModule
        call    [esp+0x18]              // GetProcAddress(hModule,"MessageBoxA"), __stdcall
        add     esp,0xC                 // pop "MessageBoxA"
 
        xor     edi,edi
        push    edi                     // "\0"
        push    0x31323334              // "4321"
        mov     ecx,esp
        push    edi
        push    ecx
        push    ecx
        push    edi
        call    eax                     // MessagBoxA(0,"4321","4321",0), __stdcall
        add     esp,8                   // pop "4321\0"

        add     esp,8                   // pop GetProcAddress() and LoadLibrary() in stack
 
        push    0x636f7250              // "Proc"
        push    0x74697845              // "Exit"
        call    find_function           // Get ExitProcess's address, __cdecl
        add     esp,8                   // pop "ExitProc"
 
        xor     edi,edi;
        push    edi;
        call    eax;                    // ExitProcess(0)
    }
}

int main() {
    popMessageBox();
    return 0;
}

Published in 未分类