隐藏API调用的静态特征

序号调用API

DLL中存在导出函数地址表,可以直接通过序号(Ordinal)导出对应函数,从而隐藏函数名的静态特征。

image-20230320192657176

dll版本的不同会导致序号的不同,因此利用时,需要实时列举api列表,找到相应的函数提取出序号,并将序号对应的字符串和目标函数字符串进行比较。

因此,在列举序号列表和函数之外,为了满足隐藏静态特征的要求,还需要对目标字符串进行混淆,这里简单的进行xor处理

假如我们要提取的内容为user32.dll中的MessageBoxA函数,我们首先对这两个字符串进行混淆

import sys
import os
import hashlib
import string

## XOR function
def xor(data, key):
    key = str(key)
    l = len(key)
    output_str = ""

    for i in range(len(data)):
        current = data[i]
        current_key = key[i % len(key)]
        ordd = lambda x: x if isinstance(x, int) else ord(x)
        output_str += chr(ordd(current) ^ ord(current_key))
    return output_str

## encrypting
def xor_encrypt(data, key):
    ciphertext = xor(data, key)
    ciphertext = '{ 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };'
    print (ciphertext)
    return ciphertext, key

## key
my_secret_key = "thisisAtest"

ciphertext, p_key = xor_encrypt("user32.dll", my_secret_key)
ciphertext, p_key = xor_encrypt("MessageBoxA", my_secret_key)

因此在C++代码体中,这两个函数的解密过程为

// MessageBoxA
unsigned char s_mb[] = { 0x39, 0xd, 0x1a, 0x0, 0x8, 0x14, 0x24, 0x36, 0xa, 0xb, 0x35 };

// user32.dll
unsigned char s_dll[] = { 0x1, 0x1b, 0xc, 0x1, 0x5a, 0x41, 0x6f, 0x10, 0x9, 0x1f };

// key
char s_key[] = "thisisAtest";

// XOR decrypt
void XOR(char * data, size_t data_len, char * key, size_t key_len) {
  int j;
  j = 0;
  for (int i = 0; i < data_len; i++) {
    if (j == key_len - 1) j = 0;
    data[i] = data[i] ^ key[j];
    j++;
  }
}

int main(){
    XOR((char *) s_dll, sizeof(s_dll), s_key, sizeof(s_key));
    XOR((char *) s_mb, sizeof(s_mb), s_key, sizeof(s_key));
}

然后使用 Name Pointer Table (NPT) 和 Export Ordinal Table (EOT) 来找到导出序号:

c++ - Getting ordinal from function name programmatically - Stack Overflow

结合起来,完整代码:

#include <stdio.h>
#include "windows.h"

typedef UINT(CALLBACK* fnMessageBoxA)(
    HWND   hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT   uType
    );

// MessageBoxA
unsigned char s_mb[] = { 0x39, 0xd, 0x1a, 0x0, 0x8, 0x14, 0x24, 0x36, 0xa, 0xb, 0x35 };

// user32.dll
unsigned char s_dll[] = { 0x1, 0x1b, 0xc, 0x1, 0x5a, 0x41, 0x6f, 0x10, 0x9, 0x1f };

// key
char s_key[] = "thisisAtest";

// XOR decrypt
void XOR(char* data, size_t data_len, char* key, size_t key_len) {
    int j;
    j = 0;
    for (int i = 0; i < data_len; i++) {
        if (j == key_len - 1) j = 0;
        data[i] = data[i] ^ key[j];
        j++;
    }
}

// binary search
DWORD FindNptProc(PDWORD npt, DWORD size, PBYTE base, LPCSTR proc)
{
    INT   cmp;
    DWORD max;
    DWORD mid;
    DWORD min;

    min = 0;
    max = size - 1;

    while (min <= max) {
        mid = (min + max) >> 1;
        cmp = strcmp((LPCSTR)(npt[mid] + base), proc);
        if (cmp < 0) {
            min = mid + 1;
        }
        else if (cmp > 0) {
            max = mid - 1;
        }
        else {
            return mid;
        }
    }

    return -1;
}

/// Gets a pointer to a module's export directory table (EDT).
///
/// @param[in] module    Handle to the module (as returned by GetModuleHandle).
///
/// @return    Returns a pointer to the module's EDT. If there is an error (e.g.
///            if the module handle is invalid or the module has no EDT) the
///            function will return NULL.
///
PIMAGE_EXPORT_DIRECTORY GetExportDirectoryTable(HMODULE module)
{
    PBYTE                   base; // base address of module
    PIMAGE_FILE_HEADER      cfh;  // COFF file header
    PIMAGE_EXPORT_DIRECTORY edt;  // export directory table (EDT)
    DWORD                   rva;  // relative virtual address of EDT
    PIMAGE_DOS_HEADER       mds;  // MS-DOS stub
    PIMAGE_OPTIONAL_HEADER  oh;   // so-called "optional" header
    PDWORD                  sig;  // PE signature

    // Start at the base of the module. The MS-DOS stub begins there.
    base = (PBYTE)module;
    mds = (PIMAGE_DOS_HEADER)module;

    // Get the PE signature and verify it.
    sig = (DWORD*)(base + mds->e_lfanew);
    if (IMAGE_NT_SIGNATURE != *sig) {
        // Bad signature -- invalid image or module handle
        return NULL;
    }

    // Get the COFF file header.
    cfh = (PIMAGE_FILE_HEADER)(sig + 1);

    // Get the "optional" header (it's not actually optional for executables).
    oh = (PIMAGE_OPTIONAL_HEADER)(cfh + 1);

    // Finally, get the export directory table.
    if (IMAGE_DIRECTORY_ENTRY_EXPORT >= oh->NumberOfRvaAndSizes) {
        // This image doesn't have an export directory table.
        return NULL;
    }
    rva = oh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    edt = (PIMAGE_EXPORT_DIRECTORY)(base + rva);

    return edt;
}

/// Gets the ordinal of an exported procedure.
///
/// @param[in] module    Handle (as returned by GetModuleHandle) of the module
///                      that exports the procedure.
///
/// @param[in] proc      String containing the name of the procedure.
///
/// @return    Returns the procedure's ordinal. If an ordinal for the procedure
///            could not be located (e.g. if the named procedure is not exported
///            by the specified module) then the function will return -1.
///
DWORD GetProcOrdinal(HMODULE module, LPCSTR proc)
{
    PBYTE                   base; // module base address
    PIMAGE_EXPORT_DIRECTORY edt;  // export directory table (EDT)
    PWORD                   eot;  // export ordinal table (EOT)
    DWORD                   i;    // index into NPT and/or EOT
    PDWORD                  npt;  // name pointer table (NPT)

    base = (PBYTE)module;

    // Get the export directory table, from which we can find the name pointer
    // table and export ordinal table.
    edt = GetExportDirectoryTable(module);

    // Get the name pointer table and search it for the named procedure.
    npt = (DWORD*)(base + edt->AddressOfNames);
    i = FindNptProc(npt, edt->NumberOfNames, base, proc);
    if (-1 == i) {
        // The procedure was not found in the module's name pointer table.
        return -1;
    }

    // Get the export ordinal table.
    eot = (WORD*)(base + edt->AddressOfNameOrdinals);

    // Actual ordinal is ordinal from EOT plus "ordinal base" from EDT.
    return eot[i] + edt->Base;
}

int main(int argc, char* argv[]) {
    XOR((char*)s_dll, sizeof(s_dll), s_key, sizeof(s_key));
    XOR((char*)s_mb, sizeof(s_mb), s_key, sizeof(s_key));

    if (NULL == LoadLibrary((LPCSTR)s_dll)) {
        printf("failed to load library :( %s\n", s_dll);
        return -2;
    }

    HMODULE module = GetModuleHandle((LPCSTR)s_dll);
    if (NULL == module) {
        printf("failed to get a handle to %s\n", s_dll);
        return -2;
    }

    DWORD ord = GetProcOrdinal(module, (LPCSTR)s_mb);
    if (-1 == ord) {
        printf("failed to find ordinal %s\n", s_mb);
        return -2;
    }
    //偷梁换柱
    fnMessageBoxA myMessageBoxA = (fnMessageBoxA)GetProcAddress(module, (LPCSTR)MAKEINTRESOURCE(ord));
    myMessageBoxA(NULL, "我弹出来了", "边框", MB_OK);
    return 0;
}

image-20230320200015919

成功隐藏user32.dll以及调用的函数

image-20230320200120640

自定义混淆调用API

除了利用既定的规则,可以尝试自定义一些函数方法来混淆需要调用的目标函数,以此隐藏静态特征

例如这里定义一个移位混淆

DWORD calcMyHash(char* data) {
  DWORD hash = 0x35;
  for (int i = 0; i < strlen(data); i++) {
    hash += data[i] + (hash << 1);
  }
  return hash;
}

然后获取 DLL 的 DOS 头、NT 头和导出表,遍历导出表中的函数名称,使用 calcMyHash 函数计算每个函数名称的哈希值,如果和给定的 myHash 值相等,就返回该函数的地址。否则,返回空指针

static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
    PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
    PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)((LPBYTE)h + img_dos_header->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
        (LPBYTE)h + img_nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD fAddr = (PDWORD)((LPBYTE)h + img_edt->AddressOfFunctions);
    PDWORD fNames = (PDWORD)((LPBYTE)h + img_edt->AddressOfNames);
    PWORD  fOrd = (PWORD)((LPBYTE)h + img_edt->AddressOfNameOrdinals);

    for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {
        LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);

        if (calcMyHash(pFuncName) == myHash) {
            printf("successfully found! %s - %d\n", pFuncName, myHash);
            return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
        }
    }
    return nullptr;
}

完整代码

#include <windows.h>
#include <stdio.h>

typedef UINT(CALLBACK* fnMessageBoxA)(
    HWND   hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT   uType
    );

DWORD calcMyHash(char* data) {
    DWORD hash = 0x35;
    for (int i = 0; i < strlen(data); i++) {
        hash += data[i] + (hash << 1);
    }
    return hash;
}

static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
    PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
    PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)((LPBYTE)h + img_dos_header->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
        (LPBYTE)h + img_nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD fAddr = (PDWORD)((LPBYTE)h + img_edt->AddressOfFunctions);
    PDWORD fNames = (PDWORD)((LPBYTE)h + img_edt->AddressOfNames);
    PWORD  fOrd = (PWORD)((LPBYTE)h + img_edt->AddressOfNameOrdinals);

    for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {
        LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);

        if (calcMyHash(pFuncName) == myHash) {
            printf("successfully found! %s - %d\n", pFuncName, myHash);
            return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
        }
    }
    return nullptr;
}

int main() {
    HMODULE mod = LoadLibrary((LPCSTR)"user32.dll");
    LPVOID addr = getAPIAddr(mod, 17036696);
    printf("0x%p\n", addr);
    fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
    myMessageBoxA(NULL, "我弹出来了", "边框", MB_OK);
    return 0;
}

image-20230320202211811

image-20230320202232981

《隐藏API调用的静态特征》有1条评论

发表评论