Aether

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;
}