序号调用API
DLL中存在导出函数地址表,可以直接通过序号(Ordinal)导出对应函数,从而隐藏函数名的静态特征。
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;
}
成功隐藏user32.dll以及调用的函数
自定义混淆调用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;
}
tql