wanheiqiyihu
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

导入表
在 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函数
相对隐藏
隐藏的方法很简单
在普通的 Windows 程序中,当编译器编译代码时,如果代码里调用了某个 DLL 中的函数(如MessageBoxA
),编译器会在生成的可执行文件(PE 文件)里添加相应的导入表项。
我们可以不依赖编译器自动生成的导入表来调用MessageBoxA
函数,而是手动完成。
手动加载dll,然后在dll中找到函数地址,把函数地址赋值给指针,然后调用该函数。
首先我们要做的就是自己定义一个messageboxa函数的指针类型,我们需要看messagebox要传入哪些参数进去,然后用结构体去定义。至于在哪里去看,当然是翻官方的文档了,文档地址如下:MessageBoxA 函数 (winuser.h) - Win32 apps | Microsoft Learn
我们直接根据他的描述,定一个新的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;
}
这个时候再用CFF去看就看不到user32.dll了,但是这只是相对隐藏掉了。
但是如果直接去搜exe里面的字符串,还是能看到messagebox这个函数。我们用die工具去进行搜索。可以看到同样还是有这么个东西
加密
我们可以通过加密的方式去隐藏这个字符串。直接使用大部分网站的密码存储思路,通过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;
}
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);
}
成功弹窗
这时候不管cff还是die搜字符串,都看不到我们调用的函数了
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)