从零讲解隐藏导入表

2025-03-20 57 0

导入表

在 Windows 操作系统中,可执行文件(如.exe文件)和动态链接库(DLL,.dll文件)常常需要调用其他 DLL 中的函数来实现特定功能。程序的导入表(Import Table)就是用于记录这些外部依赖信息的一种数据结构

首先我们这里用一个实验来解释一下什么是程序的导入表

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

int main() {
    // 调用 MessageBoxA 函数(ANSI 版本)
    int result = MessageBoxA(NULL, "这是控制台程序中弹出的消息框。", "消息提示", MB_OKCANCEL);

    return 0;
}

这里就是直接用messagebox函数,输出一个弹窗,众所周知,messagebox函数是存在user32.dll里面的,理论上讲,我们通过查看这个程序的导入表,就能看到它调用了user32.dll。现在我们用CFF Explorer去看看

从零讲解隐藏导入表插图

在导入表这里可以看到确实是有user32.dll以及messageboxa函数

从零讲解隐藏导入表插图1

相对隐藏

隐藏的方法很简单

在普通的 Windows 程序中,当编译器编译代码时,如果代码里调用了某个 DLL 中的函数(如MessageBoxA),编译器会在生成的可执行文件(PE 文件)里添加相应的导入表项。

我们可以不依赖编译器自动生成的导入表来调用MessageBoxA函数,而是手动完成。

手动加载dll,然后在dll中找到函数地址,把函数地址赋值给指针,然后调用该函数。

首先我们要做的就是自己定义一个messageboxa函数的指针类型,我们需要看messagebox要传入哪些参数进去,然后用结构体去定义。至于在哪里去看,当然是翻官方的文档了,文档地址如下:MessageBoxA 函数 (winuser.h) - Win32 apps | Microsoft Learn

从零讲解隐藏导入表插图2

我们直接根据他的描述,定一个新的messagebox的类型,fnMessageBoxA就是我们定义的新名字

// 定义 MessageBoxA 函数指针类型
typedef UINT(CALLBACK* fnMessageBoxA)(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
    );

然后就是加载dll,使用LoadLibraryA函数即可

HMODULE hUser32 = LoadLibraryA("user32.dll");

然后从dll中寻找函数地址,具体代码和解释如下

简单的讲,就是先通过dos头的偏移量获取到nt头的地址,然后nt头里面就记录了记录了导出表的位置。在找到导出表之后,继续找到导出表里面的名称表,函数地址表。然后直接遍历即可

// 从指定模块中查找指定名称的函数地址
// 参数:
// hModule: 要查找函数的模块句柄,通常由 LoadLibrary 或 GetModuleHandle 等函数获取
// functionName: 要查找的函数的名称,以字符串形式传入
// 返回值:
// 如果找到目标函数,返回其地址(FARPROC 类型);如果未找到或模块句柄无效,返回 NULL
FARPROC GetFunctionAddress(HMODULE hModule, const char* functionName) {
    // 获取模块的导出表信息
    // 将模块句柄强制转换为 PIMAGE_DOS_HEADER 类型,以便访问 DOS 头信息
    // 每个 PE 文件(如 DLL、EXE)开头都有一个 DOS 头,包含基本信息如文件签名等
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;

    // pDosHeader->e_lfanew 表示 NT 头相对于文件起始位置的偏移量
    // 通过将模块句柄加上这个偏移量,定位到 NT 头的位置
    // NT 头包含了更多关于 PE 文件的详细信息,如可选头、节表等
    PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)hModule + pDosHeader->e_lfanew);

    // pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
    // 表示导出表相对于模块基地址的虚拟地址
    // 通过将模块句柄加上这个虚拟地址,定位到导出表的位置
    // 导出表记录了模块中导出的函数和变量的信息
    PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModule + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    // 获取导出函数的名称表、地址表和序号表

    // pExportDirectory->AddressOfNames 是导出表中名称表的相对虚拟地址
    // 通过将模块句柄加上这个地址,得到名称表的实际地址
    // 名称表是一个数组,每个元素是一个指向函数名称字符串的偏移量
    PDWORD pNames = (PDWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfNames);

    // pExportDirectory->AddressOfNameOrdinals 是导出表中名称序号表的相对虚拟地址
    // 名称序号表也是一个数组,每个元素是一个序号,用于关联名称表和函数地址表
    PWORD pNameOrdinals = (PWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfNameOrdinals);

    // pExportDirectory->AddressOfFunctions 是导出表中函数地址表的相对虚拟地址
    // 函数地址表存储了每个导出函数的实际地址
    PDWORD pFunctions = (PDWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfFunctions);

    // 遍历导出函数名称表,查找目标函数
    // pExportDirectory->NumberOfNames 表示导出表中函数名称的数量
    for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
        // pNames[i] 是当前函数名称相对于模块基地址的偏移量
        // 通过将模块句柄加上这个偏移量,得到函数名称字符串的实际地址
        const char* currentFunctionName = (const char*)((DWORD_PTR)hModule + pNames[i]);

        // 使用 strcmp 函数比较当前函数名称和要查找的函数名称
        // 如果两者相等,说明找到了目标函数
        if (strcmp(currentFunctionName, functionName) == 0) {
            // pNameOrdinals[i] 存储了当前函数的序号
            // 获取当前函数的序号
            WORD ordinal = pNameOrdinals[i];

            // pFunctions[ordinal] 是目标函数相对于模块基地址的偏移量
            // 通过将模块句柄加上这个偏移量,得到目标函数的实际地址
            // 并将其作为 FARPROC 类型返回
            return (FARPROC)((DWORD_PTR)hModule + pFunctions[ordinal]);
        }
    }
    // 如果遍历完所有函数名称都没有找到目标函数
    // 则返回 NULL 表示查找失败
    return NULL;
}

最后找到函数地址之后,转换成我们自己定义的指针,然后直接调用即可

// 从 user32.dll 中查找 MessageBoxA 函数的地址
    FARPROC messageBoxAddr = GetFunctionAddress(hUser32, "MessageBoxA");
    
    // 将函数地址转换为函数指针类型
    fnMessageBoxA myMessageBoxA = (fnMessageBoxA)messageBoxAddr;

    // 调用 MessageBoxA 函数
    myMessageBoxA(NULL, "11111", "1111", MB_OK);

    // 释放加载的 DLL
    FreeLibrary(hUser32);

最后完整代码如下

#include <windows.h>
#include <stdio.h>
typedef UINT(CALLBACK* fnMessageBoxA)(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
    );
FARPROC GetFunctionAddress(HMODULE hModule, const char* functionName) {

    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)hModule + pDosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModule + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    PDWORD pNames = (PDWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfNames);
    PWORD pNameOrdinals = (PWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfNameOrdinals);
    PDWORD pFunctions = (PDWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfFunctions);

    for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
        const char* currentFunctionName = (const char*)((DWORD_PTR)hModule + pNames[i]);
        if (strcmp(currentFunctionName, functionName) == 0) {
            WORD ordinal = pNameOrdinals[i];
            return (FARPROC)((DWORD_PTR)hModule + pFunctions[ordinal]);
        }
    }

    return NULL;
}

int main() {
    HMODULE hUser32 = LoadLibraryA("user32.dll");
    FARPROC messageBoxAddr = GetFunctionAddress(hUser32, "MessageBoxA");
    fnMessageBoxA myMessageBoxA = (fnMessageBoxA)messageBoxAddr;
    myMessageBoxA(NULL, "11111", "1111", MB_OK);
    FreeLibrary(hUser32);
    return 0;
}

从零讲解隐藏导入表插图3

这个时候再用CFF去看就看不到user32.dll了,但是这只是相对隐藏掉了。

从零讲解隐藏导入表插图4

但是如果直接去搜exe里面的字符串,还是能看到messagebox这个函数。我们用die工具去进行搜索。可以看到同样还是有这么个东西

从零讲解隐藏导入表插图5

加密

我们可以通过加密的方式去隐藏这个字符串。直接使用大部分网站的密码存储思路,通过hash加密,然后对比加密后的数据来判断两个数据是不是一样的。

那我们这里在遍历的时候,每次遍历到的函数名的时候就hash加密,然后和hash加密后的messageboxa进行对比。

我们这里简单的写一个hash算法,然后通过这个hash算法去进行比对

#include <stdio.h>
#include <string.h>

// 旋转哈希函数
unsigned int rotatingHash(const char* str) {
    unsigned int hash = 0;
    int i;
    for (i = 0; str[i] != '\0'; i++) {
        hash = (hash << 4) ^ (hash >> 28) ^ str[i];
    }
    return hash;
}

int main() {
    const char* input = "MessageBoxA";
    unsigned int hash = rotatingHash(input);
    printf(" '%s' is: %u\n", input, hash);
    return 0;
}

从零讲解隐藏导入表插图6

MessageBoxA的hash值是1460732901

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

typedef UINT(CALLBACK* fnMessageBoxA)(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
    );

unsigned int rotatingHash(const char* str) {
    unsigned int hash = 0;
    int i;
    for (i = 0; str[i] != '\0'; i++) {
        hash = (hash << 4) ^ (hash >> 28) ^ str[i];
    }
    return hash;
}

static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
    PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
    PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)((LPBYTE)h + img_dos_header->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
        (LPBYTE)h + img_nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD fAddr = (PDWORD)((LPBYTE)h + img_edt->AddressOfFunctions);
    PDWORD fNames = (PDWORD)((LPBYTE)h + img_edt->AddressOfNames);
    PWORD fOrd = (PWORD)((LPBYTE)h + img_edt->AddressOfNameOrdinals);

    for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {
        LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);
        if (rotatingHash(pFuncName) == myHash) {
            printf("successfully found! %s - %d\n", pFuncName, myHash);
            return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
        }
    }
    return NULL;
}

void main() {
    HMODULE mod = LoadLibraryA("user32.dll");
    LPVOID addr = getAPIAddr(mod, 1460732901);
    printf("0x%p\n", addr);
    fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
    myMessageBoxA(NULL, "这是弹窗", "嘻嘻嘻", MB_OK);
}

从零讲解隐藏导入表插图7

成功弹窗

从零讲解隐藏导入表插图8

这时候不管cff还是die搜字符串,都看不到我们调用的函数了

从零讲解隐藏导入表插图9

从零讲解隐藏导入表插图10


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

二进制分析实战笔记(二)
[Meachines] [Medium] Union UHC+SQLI文件读取+TRP00F+命令注入+sudo权限提升
【APP 逆向百例】淘某热点 APP 逆向分析
file协议小解——为什么是”file:///path”
HTB-writeup-writeup
HTB-Precious-WriteUp

发布评论