温故知新
闲聊几句
最近看到一些IAT的文章,回顾了一下项目的代码,发现之前确实有留下一些想法
- 利用 LoadLibrary() 加载 DLL 获取句柄
- 利用 GetProcAddress() 加载 DLL 对应的 API
- 通过 API 的地址去调用定义的函数
但因为Go底层本身会调用一些 API,会导致导入表导入一些不应该存在的 API,在此基础上结合这个想法和之前文章的说明,写了一个新的思路
自定义 GetProcAddress()
由于测试Go的时间比较长,所以这次使用C++来实现
先不探讨寻找函数基址的原理,简单写一下应用的方法
先定义需要替换的函数
FARPROC GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName) {
// 避免每次使用 hModule 时进行强制转换
PBYTE pBase = (PBYTE) hModule;
// 获取 DOS 头并进行签名检查
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER) pBase;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
// 获取 NT 头并进行签名检查
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS) ( pBase + pImgDosHdr->e_lfanew );
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// 获取可选头
IMAGE_OPTIONAL_HEADER ImgOptHdr = pImgNtHdrs->OptionalHeader;
// 获取映像导出表
PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY) ( pBase + ImgOptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress );
// 获取函数名数组指针
PDWORD FunctionNameArray = (PDWORD) ( pBase + pImgExportDir->AddressOfNames );
// 获取函数地址数组指针
PDWORD FunctionAddressArray = (PDWORD) ( pBase + pImgExportDir->AddressOfFunctions );
// 获取函数序号数组指针
PWORD FunctionOrdinalArray = (PWORD) ( pBase + pImgExportDir->AddressOfNameOrdinals );
// 遍历所有导出的函数
for (DWORD i = 0; i < pImgExportDir->NumberOfFunctions; i++) {
// 获取函数名
CHAR* pFunctionName = (CHAR*) ( pBase + FunctionNameArray[i] );
// 通过其序号获取函数地址
PVOID pFunctionAddress = (PVOID) ( pBase + FunctionAddressArray[FunctionOrdinalArray[i]] );
// 查找指定的函数
if (strcmp(lpApiName, pFunctionName) == 0) {
//printf("[ %0.4d ] FOUND API -\ NAME: %s -\t ADDRESS: 0x%p -\t ORDINAL: %d\n", i, pFunctionName, pFunctionAddress, FunctionOrdinalArray[i]);
printf("[*] FOUND API - NAME: %s \n", pFunctionName);
printf("[*] FOUND API - ADDRESS: 0x%p \n", pFunctionAddress);
printf("[*] FOUND API - ORDINAL: %d \n", FunctionOrdinalArray[i]);
return FARPROC(pFunctionAddress);
}
}
return NULL;
}
调用替换的自定义函数
// 加载一个 DLL 文件,例如 User32.dll
HMODULE hModule = LoadLibraryA("user32.dll");
if (hModule == NULL) {
printf("Failed to load library.\n");
return 1;
}
// 调用 GetProcAddressReplacement 函数,查找 MessageBoxA 函数的地址
FARPROC pFunc = GetProcAddressReplacement(hModule, "MessageBoxA");
if (pFunc != NULL) {
printf("Address of MessageBoxA: 0x%p\n", pFunc);
} else {
printf("Function not found.\n");
}
// 释放 DLL
FreeLibrary(hModule);
return 0;
}
或者是直接定义函数指针类型进行调用
FARPROC pFunc = GetProcAddressReplacement(hModule, "MessageBoxA");
if (pFunc != NULL) {
// 定义函数指针类型
typedef int ( WINAPI* MessageBoxA_t )( HWND, LPCSTR, LPCSTR, UINT );
// 将函数地址转换为相应的函数指针类型
MessageBoxA_t MessageBoxAReplacement = (MessageBoxA_t) pFunc;
// 调用 MessageBoxA
MessageBoxAReplacement(NULL, "Hello, World!", "Test", MB_OK);
}
else {
printf("Function not found.\n");
}
亦或者是写成一个 自定义 API,且使用字符串数组的方式规避静态的扫描
typedef int ( WINAPI* MessageBoxA_t )( HWND, LPCSTR, LPCSTR, UINT );
void XMessageBox(HMODULE hModule, LPCSTR message, LPCSTR title) {
// 查找 MessageBoxA 函数地址
char MessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
FARPROC pFunc = GetProcAddress(hModule, MessageBoxA);
if (pFunc != NULL) {
MessageBoxA_t MessageBoxA = (MessageBoxA_t) pFunc;
// 调用 MessageBoxA
MessageBoxA(NULL, message, title, MB_OK);
}
else {
printf("Function not found.\n");
}
}
再检查一下导入表
将这个写法写成一个C++库就可以批量调用Windows APIs 而不会出现在导入表中。但是可能会被查杀 LoadLibrary() ,所以还是要考虑能不能隐藏掉
其实有看过这篇文章,但是我忘记了在哪了 : (
总结
由于Go编译的原因,导入表会出现很多敏感的API
在IAT这块,可能C++更有优势,但Go其实也不输C++
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
请登录后查看评论内容