在今年上半年,我们介绍通过其他形式shellcode的转换+系统回调函数的方式来加载shellcode绕过杀毒软件,随着时间的推移,部分系统回调函数和创建内存的函数已经被列入黑名单中。杀毒软件可以通过计算PE文件的导入表(import address tables)的哈希值来判断该程序是否调用这些敏感函数,进而判断是否为危险程序,本文将介绍通过函数指针的方式调用系统函数,从而隐藏程序导入表。
VS2022自带的dumpbin.exe (Path: [vs2022]\VC\Tools\MSVC\14.31.31103\bin\Hostx64\x64\dumpbin.exe) 或 PE查看工具
dumpbin.exe /imports Project3.exe例如我们查看回调函数V0.2版本中的导入表
从中可以看出几个目前已经标记为敏感调用的函数,
01: KERNEL32.dll下用来创建、分配内存的HeapCreate、HeapAlloc;
02: USER32.dll下用来加载shellcode的回调函数EnumChildWindows;
03: RPCTR4.dll下用来shellcode转换的UuidFromStringA。
为了避免杀毒软件直接通过PE文件中的导入表进行查杀,我们使用函数指针动态加载这些系统函数。
GetModuleHandle(TEXT("kernel32.dll")) : 获取kernel32模块,注意必须已由调用进程下模块加载(当前同一模块)
LoadLibraryA("RPCRT4.dll") : 加载指定的RPCRT4模块,可以是其他模块
其中从上图的导入表中可以看出,USER32.dll和RPCRT4.dll中只调用单个函数(说明进程在调用这些函数时是单独加载的模块),也就是这个模块是需要单独指定加载的,因此只能使用LoadLibraryA而不能使用GetModuleHandle。
GetProcAddress(模块的句柄, 函数名) : 获取系统函数的地址,从而实现动态加载系统函数。
在VS2022中可以通过"Ctrl+左边"的方式查看原先系统函数的定义,例如HeapCreate函数的定义为
因此我们可以直接复制并修改,定义一个函数指针ImportHeapCreate,其中的参数以及返回值可以参考上图。
typedef HANDLE(WINAPI* ImportHeapCreate)(
_In_ DWORD flOptions,
_In_ SIZE_T dwInitialSize,
_In_ SIZE_T dwMaximumSize
);以 HeapCreate、HeapAlloc、UuidFromStringA这三个函数为例
// 1. 先根据③中的方法,定义函数指针
typedef HANDLE(WINAPI* ImportHeapCreate)(
_In_ DWORD flOptions,
_In_ SIZE_T dwInitialSize,
_In_ SIZE_T dwMaximumSize
);
typedef LPVOID(WINAPI* ImportHeapAlloc)(
_In_ HANDLE hHeap,
_In_ DWORD dwFlags,
_In_ SIZE_T dwBytes
);
typedef RPC_STATUS(RPC_ENTRY* ImportUuidFromStringA)(
_In_opt_ RPC_CSTR StringUuid,
_Out_ UUID __RPC_FAR* Uuid
);
// 2. 通过获取模块句柄(GetModuleHandle、LoadLibraryA),再通过GetProcAddress导出函数地址
int main()
{
// 通过GetModuleHandle+GetProcAddress的方式
ImportHeapCreate MyHeapCreate = (ImportHeapCreate)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "HeapCreate");
ImportHeapAlloc MyHeapAlloc = (ImportHeapAlloc)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "HeapAlloc");
// 通过LoadLibraryA+GetProcAddress的方式
HMODULE hModule = LoadLibraryA("RPCRT4.dll");
ImportUuidFromStringA MyUuidFromStringA = (ImportUuidFromStringA)GetProcAddress(hModule, "UuidFromStringA");
// ...
// ...
return 0;
}
这样一来,我们就将 HeapCreate、HeapAlloc、UuidFromStringA这三个系统函数通过地址并利用函数指针动态调用,并取名为MyHeapCreate、MyHeapAlloc、MyUuidFromStringA。在完整的代码编译后,我们再查询下导入表。(此时在导入表中已经隐藏了HeapCreate、HeapAlloc、UuidFromStringA)
与此同时,由于时间的推移,我们将之前回调函数中的所有版本进行隐藏导入表的操作,并通过VT查杀的结果,实验对比出具体存在敏感的函数。
| 版本号 | 写入内存方式 | VT查杀率 | 时间 | 火绒 | 360 | 腾讯 | 代码 |
|---|---|---|---|---|---|---|---|
| 0.1 | uuid转化(UuidFromStringA)+隐藏导入表 | 7/68 | 2022-07-10 | √ | √ | √ | c++ |
| 0.2 | base64+uuid转化(UuidFromStringA)+隐藏导入表 | 3/68 | 2022-07-10 | √ | √ | √ | c++ |
| 0.3 | ipv6转化(RtlIpv6StringToAddressA)+隐藏导入表 | 6/68 | 2022-07-10 | √ | √ | √ | c++ |
| 0.4 | mac转化(RtlEthernetStringToAddressA)+隐藏导入表 | 7/68 | 2022-07-10 | √ | √ | √ | c++ |
| 0.5 | ipv4转化(RtlIpv4StringToAddressA)+隐藏导入表 | 17/68 | 2022-07-10 | √ | X | √ | c++ |
各个版本的使用方法如同以往一致。
① HeapCreate、HeapAlloc与部分回调函数组合使用时,会被静态查杀到,可以通过隐藏导入表的方式绕过。
② 其中shellcode转换为IPV4(版本0.5),其ipv4数组的前几个ip地址特征明显且已经被大多数杀毒软件标记,所以该方法查杀率较高,可以通过**分离shellcode(**分离ipv4数组)的方式或其他编码的形式绕过检测。
③ 大部分回调函数没有被标记为敏感函数。
此外,隐藏导入表是一个综合的方法,可以使用在任何你想调用的系统函数当中,以函数指针执行动态获取地址的方式达到隐藏。


