API Resolution Detection – Samples
During the development phase of Aether v0.9, I have used the following samples as part of supporting evidence for detecting API resolution malware techniques.
// implant_hash.c
//
#include <winsock2.h>
#include <windows.h>
#include <winternl.h>
#include <wininet.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct _LDR_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
} LDR_ENTRY;
static inline uint32_t ror13(uint32_t x) {
return (x >> 13) | (x << (32 - 13));
}
static uint32_t hash_modw(const wchar_t *s) {
uint32_t h = 0;
for (;;) {
wchar_t c = *s++;
if (c >= L'a' && c <= L'z') c -= 0x20;
h = ror13(h) + (uint32_t)(uint16_t)c;
if (c == 0) break;
}
return h;
}
static uint32_t hash_export(const char *s) {
uint32_t h = 0;
for (;;) {
unsigned char c = (unsigned char)*s++;
h = ror13(h) + (uint32_t)c;
if (c == 0) break;
}
return h;
}
// ----- PEB walk -------------------------------------------------------------
static PEB *get_peb(void) {
#if defined(_M_X64) || defined(__x86_64__)
return (PEB *)__readgsqword(0x60);
#elif defined(_M_IX86) || defined(__i386__)
return (PEB *)__readfsdword(0x30);
#else
# error unsupported architecture
#endif
}
static void *find_module_by_hash(uint32_t target) {
PEB *peb = get_peb();
if (!peb || !peb->Ldr) return NULL;
LIST_ENTRY *head = &peb->Ldr->InMemoryOrderModuleList;
for (LIST_ENTRY *e = head->Flink; e != head; e = e->Flink) {
LDR_ENTRY *m = CONTAINING_RECORD(e, LDR_ENTRY, InMemoryOrderLinks);
if (!m->BaseDllName.Buffer) continue;
if (hash_modw(m->BaseDllName.Buffer) == target) return m->DllBase;
}
return NULL;
}
// ----- export table walk ----------------------------------------------------
static void *resolve_export_by_hash(void *base, uint32_t target) {
if (!base) return NULL;
BYTE *b = (BYTE *)base;
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)b;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(b + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL;
IMAGE_DATA_DIRECTORY *dd =
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!dd->VirtualAddress || !dd->Size) return NULL;
IMAGE_EXPORT_DIRECTORY *exp =
(IMAGE_EXPORT_DIRECTORY *)(b + dd->VirtualAddress);
DWORD *funcs = (DWORD *)(b + exp->AddressOfFunctions);
DWORD *names = (DWORD *)(b + exp->AddressOfNames);
WORD *ords = (WORD *)(b + exp->AddressOfNameOrdinals);
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
const char *name = (const char *)(b + names[i]);
if (hash_export(name) != target) continue;
DWORD rva = funcs[ords[i]];
// skip forwarded exports (RVA inside the export directory itself)
if (rva >= dd->VirtualAddress &&
rva < dd->VirtualAddress + dd->Size) {
return NULL;
}
return b + rva;
}
return NULL;
}
typedef HMODULE (WINAPI *fn_LoadLibraryA)(LPCSTR);
typedef LPVOID (WINAPI *fn_VirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL (WINAPI *fn_VirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD);
typedef BOOL (WINAPI *fn_VirtualFree)(LPVOID, SIZE_T, DWORD);
typedef HANDLE (WINAPI *fn_CreateFileA)(LPCSTR, DWORD, DWORD,
LPSECURITY_ATTRIBUTES, DWORD,
DWORD, HANDLE);
typedef BOOL (WINAPI *fn_CloseHandle)(HANDLE);
typedef void (WINAPI *fn_Sleep)(DWORD);
typedef LONG (NTAPI *fn_NtDelayExecution)(BOOLEAN, PLARGE_INTEGER);
typedef LONG (NTAPI *fn_NtAllocateVirtualMemory)(HANDLE, PVOID *,
ULONG_PTR, PSIZE_T,
ULONG, ULONG);
typedef HINTERNET (WINAPI *fn_InternetOpenA)(LPCSTR, DWORD, LPCSTR,
LPCSTR, DWORD);
typedef BOOL (WINAPI *fn_InternetCloseHandle)(HINTERNET);
typedef int (WINAPI *fn_WSAStartup)(WORD, LPWSADATA);
typedef int (WINAPI *fn_WSACleanup)(void);
typedef SOCKET (WINAPI *fn_socket)(int, int, int);
typedef int (WINAPI *fn_closesocket)(SOCKET);
typedef struct WINAPIFUNC {
HMODULE hKernel32;
fn_VirtualAlloc pVirtualAlloc;
fn_VirtualProtect pVirtualProtect;
fn_VirtualFree pVirtualFree;
fn_CreateFileA pCreateFileA;
fn_CloseHandle pCloseHandle;
fn_Sleep pSleep;
HMODULE hNtdll;
fn_NtDelayExecution pNtDelayExecution;
fn_NtAllocateVirtualMemory pNtAllocateVirtualMemory;
HMODULE hWininet;
fn_InternetOpenA pInternetOpenA;
fn_InternetCloseHandle pInternetCloseHandle;
HMODULE hWs2_32;
fn_WSAStartup pWSAStartup;
fn_WSACleanup pWSACleanup;
fn_socket psocket;
fn_closesocket pclosesocket;
} WINAPIFUNC;
#define MUST(F, mod, name) \
do { \
F->p##name = (fn_##name)resolve_export_by_hash( \
F->h##mod, hash_export(#name)); \
if (!F->p##name) { \
fprintf(stderr, "[implant_hash] resolve failed: %s!%s\n", \
#mod, #name); \
return NULL; \
} \
} while (0)
static WINAPIFUNC *resolve_api_table(void) {
// step 1: kernel32 + ntdll are always already loaded - locate by hash.
void *kernel32 = find_module_by_hash(hash_modw(L"KERNEL32.DLL"));
void *ntdll = find_module_by_hash(hash_modw(L"ntdll.dll"));
if (!kernel32 || !ntdll) {
fprintf(stderr, "[implant_hash] PEB walk: kernel32/ntdll missing\n");
return NULL;
}
// step 2: resolve LoadLibraryA from kernel32 by export hash, then use it
// to bring in wininet / ws2_32. (No direct GetProcAddress / LoadLibrary
// import is generated for this binary.)
fn_LoadLibraryA pLoadLibraryA =
(fn_LoadLibraryA)resolve_export_by_hash(kernel32, hash_export("LoadLibraryA"));
if (!pLoadLibraryA) {
fprintf(stderr, "[implant_hash] could not resolve LoadLibraryA\n");
return NULL;
}
void *wininet = pLoadLibraryA("wininet.dll");
void *ws2_32 = pLoadLibraryA("ws2_32.dll");
if (!wininet || !ws2_32) {
fprintf(stderr, "[implant_hash] LoadLibraryA failed\n");
return NULL;
}
WINAPIFUNC *F = (WINAPIFUNC *)calloc(1, sizeof(*F));
if (!F) return NULL;
F->hKernel32 = (HMODULE)kernel32;
F->hNtdll = (HMODULE)ntdll;
F->hWininet = (HMODULE)wininet;
F->hWs2_32 = (HMODULE)ws2_32;
MUST(F, Kernel32, VirtualAlloc);
MUST(F, Kernel32, VirtualProtect);
MUST(F, Kernel32, VirtualFree);
MUST(F, Kernel32, CreateFileA);
MUST(F, Kernel32, CloseHandle);
MUST(F, Kernel32, Sleep);
MUST(F, Ntdll, NtDelayExecution);
MUST(F, Ntdll, NtAllocateVirtualMemory);
MUST(F, Wininet, InternetOpenA);
MUST(F, Wininet, InternetCloseHandle);
MUST(F, Ws2_32, WSAStartup);
MUST(F, Ws2_32, WSACleanup);
MUST(F, Ws2_32, socket);
MUST(F, Ws2_32, closesocket);
return F;
}
static void use_api_table(WINAPIFUNC *F) {
void *p = F->pVirtualAlloc(NULL, 4096,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (p) {
DWORD old = 0;
F->pVirtualProtect(p, 4096, PAGE_READONLY, &old);
F->pVirtualFree(p, 0, MEM_RELEASE);
}
HANDLE h = F->pCreateFileA("NUL",
GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
if (h != INVALID_HANDLE_VALUE) F->pCloseHandle(h);
LARGE_INTEGER d; d.QuadPart = -10000;
F->pNtDelayExecution(FALSE, &d);
WSADATA w;
if (F->pWSAStartup(MAKEWORD(2, 2), &w) == 0) {
SOCKET s = F->psocket(AF_INET, SOCK_STREAM, 0);
if (s != INVALID_SOCKET) F->pclosesocket(s);
F->pWSACleanup();
}
HINTERNET hi = F->pInternetOpenA("implant-hash/1.0",
INTERNET_OPEN_TYPE_DIRECT,
NULL, NULL, 0);
if (hi) F->pInternetCloseHandle(hi);
}
int main(void) {
DWORD pid = GetCurrentProcessId();
WINAPIFUNC *F = resolve_api_table();
if (!F) return 1;
printf("[implant_hash] PID : %lu\n", pid);
printf("[implant_hash] strategy : PEB walk + ROR13 export hash\n");
printf("[implant_hash] heap struct : %p (%zu bytes)\n",
(void *)F, sizeof(*F));
printf("[implant_hash] expected L9/L10: HEAP_API_TABLE (HIGH) emitted\n\n");
printf("[implant_hash] kernel32 base : %p\n", F->hKernel32);
printf("[implant_hash] kernel32!VirtualAlloc : %p\n", F->pVirtualAlloc);
printf("[implant_hash] ntdll base : %p\n", F->hNtdll);
printf("[implant_hash] ntdll!NtDelayExecution : %p\n", F->pNtDelayExecution);
printf("[implant_hash] wininet base : %p\n", F->hWininet);
printf("[implant_hash] wininet!InternetOpenA : %p\n", F->pInternetOpenA);
printf("[implant_hash] ws2_32 base : %p\n", F->hWs2_32);
printf("[implant_hash] ws2_32!socket : %p\n", F->psocket);
printf("\n");
use_api_table(F);
printf("[implant_hash] running. scan with:\n");
printf(" Aether.exe --scan --pid %lu\n", pid);
printf("[implant_hash] press Enter to exit.\n");
fflush(stdout);
getchar();
return 0;
}