栈溢出笔记1.7 地址问题(2)

1.6节中找到了kernel32.dll的基地址,这一节,来解决第二个重要问题,即解析kernel32.dll的导出表,找到LoadLibraryA和GetProcAddress的地址。

DLL导出的函数信息位于导出表中,因此,首先,要在PE文件中找到导出表(IMAGE_EXPORT_DIRECTORY)的地址,这位于数据目录(IMAGE_DATA_DIRECTORY)中。而数据目录位于PE扩展头(IMAGE_OPTIONAL_HEADER)的最后。

下图为PE文件的结构:

栈溢出笔记1.7 地址问题(2)_第1张图片
图44

PE文件开头为一个DOS头(IMAGE_DOS_HEADER),大小固定为64个字节,其中,最后4个字节指定PE头的偏移量(IMAGE_NT_HEADER),即PE头标识(Signture)的偏移量,中间有一段大小不固定的DOS Stub。PE头标识之后是标准PE头(IMAGE_FILE_HEADER),大小固定为20字节,之后是扩展PE头(IMAGE_OPTIONAL_HEADER32),数据目录就位于扩展PE头的末尾。

数据目录包含16种(导出表,导入表,资源表等),每种占8个字节,导出表项位于第一项。图44中已经标出了数据目录距离DOS头的偏移,为120(0x78)个字节。(注:引用的原图有误,中间少算了8个字节)。这个偏移也就是导出表项的偏移,因为导出表项是第一项。

导出表项的偏移并不是导出表的偏移,每个数据目录项8个字节,共两个字段,如下:

/*****************************************************************************/
typedef struct _IMAGE_DATA_DIRECTORY {

 DWORD VirtualAddress;

 DWORD Size;

} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
/*****************************************************************************/

第一个字段VirtualAddress表示导出表距离PE文件开头的偏移,第二个字段Size为导出表的大小,根据这两个字段,可以定位导出表。

下面来看导出表的具体结构,如下:

/*****************************************************************************/
typedef struct _IMAGE_EXPORT_DIRECTORY {  
 DWORD Characteristics; 
 DWORD TimeDateStamp; 
 WORD MajorVersion; 
 WORD MinorVersion; 
 DWORD Name; 
DWORD Base;                     // +0x10 函数起始需要
DWORD NumberOfFunctions;      // +0x14 导出函数个数
 DWORD NumberOfNames; // +0x18以函数名导出的函数个数
DWORD AddressOfFunctions;      // +0x1c 导出函数地址表
DWORD AddressOfNames;          // +0x20 函数名称地址表  
DWORD AddressOfNameOrdinals;  // +0x24函数序号地址表 
} IMAGE_EXPORT_DIRECTORYM, *pIMAGE_EXPORT_DIRECTORY;
/*****************************************************************************/

标红的字段为重要的字段。其中,AddressOfFunctions所指的区域依次保存了所有导出函数的地址,个数由NumberOfFunctions决定。AddressOfNames所指的区域依次保存了对应函数的函数名,个数由NumberOfNames决定。AddressOfNameOrdinals与AddressOfNames一一对应,指定函数在AddressOfFunctions的索引值,即将函数名与地址联系起来。需要注意的是AddressOfNameOrdinals中的序号为一个字(两个字节),而不是像地址为4个字节。

栈溢出笔记1.7 地址问题(2)_第2张图片
图45

AddressOfFunctions的个数可能会大于AddressOfNames,因为有的函数可能没有导出函数名,此时,没有AddressOfNameOrdinals,就找不到函数地址了。

记住,我们要找的两个函数LoadLibraryA和GetProcAddress,我们知道的只有函数名,因此,查找从AddressOfNames开始,比较函数名,然后根据索引(第几个),在AddressOfNameOrdinals查找到函数在AddressOfFunctions的索引值(前面说了,AddressOfNames和AddressOfNameOrdinals一一对应),然后,从AddressOfFunctions找到对应索引处的地址即可。注意,PE文件中的地址均为相对地址(RVA),因此,实际使用时要加上模块(exe,dll)实际加载基地址,换为实际地址。

现在,又到了写代码实现的时候了,比较麻烦的地方是字符串的比较,查找指定名称函数的代码可以实现为一个函数,输入为函数的名称,输出为函数的地址。我们还要用到上一节的内容,即获取kernel32.dll的基地址。

/*****************************************************************************/
// example_9 从kernel32.dll中查找函数地址
#include <stdio.h>

// 获取kernel32.dll的基地址
int get_kernel32_base()
{
    __asm
    {
        mov eax, fs:[0x30]  // PEB
        mov eax, [eax+0x0c] // PEB->Ldr
        mov eax, [eax+0x1c] // PEB->Ldr.InInitializationOrderModuleList.Flink(指向第一个元素)
        mov eax, [eax]      // 指向第二个元素
        mov eax, [eax+0x08] // kernel32.dll基地址
    }
}

int compare_string( char* symbol1, char* symbol2 )
{
    __asm
    {
        mov esi, [ebp+8]; // symbol1
        mov edi, [ebp+12]; // symbol2

        xor eax, eax

    compare_loop:
        mov al, [esi] // 取下一个字符
        mov bl, [edi]
        cmp al, bl // 比较是否相等
        jnz equal_no
        test al, al // symbol2是否结尾
        jz equal_yes
        test bl, bl // symbol1是否结尾
        jz equal_yes
        inc esi 
        inc edi
        jmp compare_loop

    equal_no:
        sub al, bl
    equal_yes:

    }
}

int get_func_addr( char* symbol )
{
    __asm
    {
        call get_kernel32_base // 获取kernel32.dll基地址
        mov ebx, [eax+0x3c]   // PE头的偏移
        mov ebx, [eax+ebx+0x78] // 数据目录导出表项的偏移
        add ebx, eax  // 导出表地址
        mov ecx, [ebx+0x18] // NumberOfNames
        mov edx, [ebx+0x20] // AddressOfNames的RVA
        add edx, eax // 转换为VA

    search_loop:
        jecxz error_done
        dec ecx
        pushad
        mov esi, [edx+ecx*4]
        add esi, eax // 函数名
        push esi
        mov edi, [ebp+8];   // 参数: char* symbol
        push edi
        call compare_string // 比较函数名
        add esp, 8
        test eax, eax       // eax为则相等
        popad
        jnz search_loop
        mov edx, [ebx+0x24] // 函数序号表RVA
        add edx, eax        // 函数序号表VA
        mov cx, [edx+ecx*2] // 函数序号
        mov edx, [ebx+0x1c] // 函数地址表RVA
        add edx, eax        // 函数地址表VA
        mov edx, [edx+ecx*4] // 函数地址RVA
        add edx, eax         // 函数地址VA
        jmp done
    error_done:
        xor eax, eax
    done:
        mov eax, edx
    }
}

int main()
{
    printf("0x%x\n", get_func_addr("LoadLibraryA"));

    return 0;
}
/*****************************************************************************/

你可能感兴趣的:(windows,栈溢出,shellcode)