From e11d24d141186e6932550dda1870062a22ae3a2e Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 30 May 2026 19:12:07 +0900 Subject: [PATCH] docs(shared-skills): batch 43 (4 files) --- .../debugging/references/tools/ghidra.md | 212 ++++++++++++++ .../references/tools/playwright-cli.md | 194 +++++++++++++ .../debugging/references/tools/pwndbg.md | 263 +++++++++++++++++ .../debugging/references/tools/pwntools.md | 265 ++++++++++++++++++ 4 files changed, 934 insertions(+) create mode 100644 packages/shared-skills/skills/debugging/references/tools/ghidra.md create mode 100644 packages/shared-skills/skills/debugging/references/tools/playwright-cli.md create mode 100644 packages/shared-skills/skills/debugging/references/tools/pwndbg.md create mode 100644 packages/shared-skills/skills/debugging/references/tools/pwntools.md diff --git a/packages/shared-skills/skills/debugging/references/tools/ghidra.md b/packages/shared-skills/skills/debugging/references/tools/ghidra.md new file mode 100644 index 000000000..045cda033 --- /dev/null +++ b/packages/shared-skills/skills/debugging/references/tools/ghidra.md @@ -0,0 +1,212 @@ +# Ghidra — Decompile Binaries Into Readable C + +**https://github.com/NationalSecurityAgency/ghidra** + +Ghidra is the NSA's open-source reverse-engineering suite. Its defining feature is a **decompiler** that turns machine code back into readable C. For any binary you don't have source for, this is the correct starting point — not `strings`, not hex-staring, not `objdump -d`. + +**Use Ghidra when**: third-party closed-source libs, malware analysis, vendored binaries whose behavior contradicts docs, CTF challenges, firmware, any time you need to read compiled code. + +--- + +## Install + +```bash +# macOS +brew install --cask ghidra +# OR download the release ZIP from the repo and ./ghidraRun + +# Linux +# Download from https://github.com/NationalSecurityAgency/ghidra/releases +# Requires JDK 21+ +./ghidraRun + +# Dependency +java -version # must be 21+ +``` + +Ghidra is a Java Swing app. Looks dated, works well. + +--- + +## First-time workflow (memorize this — it's not obvious) + +1. **Start Ghidra**: `ghidraRun` +2. **Create a project**: File → New Project → Non-Shared → name it `debug-` (journal this path so you can rm it at Phase 9 if disposable). +3. **Import the binary**: File → Import File → pick your target. Accept default format detection. +4. **Double-click the imported binary** in the project listing. Ghidra asks to analyze it — say **yes**, accept defaults for the first pass. This takes anywhere from seconds (small binary) to tens of minutes (large binary). +5. **Once analysis completes**, you're in the CodeBrowser view. + +Two panels you'll use 95% of the time: + +- **Listing** (middle) — the disassembly with Ghidra's inferred labels/types. +- **Decompiler** (right) — the reconstructed C. The real value. + +--- + +## Finding the right function fast + +Don't try to read the whole binary. Use these to narrow: + +### Symbol Tree (left panel) + +- `Functions` — all detected functions. Stripped binaries show `FUN_00401234` (address-named); unstripped show actual names. +- `Imports` — dynamically-linked functions. Great for "does this binary call `system()`, `strcpy`, `curl_easy_perform`?" +- `Exports` — if it's a library. + +Click to jump. The Decompiler updates instantly. + +### String search + +``` +Search → For Strings +``` + +Produces a list of all strings. Right-click a string → `References` → `Show References to Address`. Jumps to code that references it. **This is how you find which function handles the error message you saw at runtime.** + +### Memory search for bytes + +``` +Search → Memory +``` + +Search hex or text. Useful for known magic bytes, file-format signatures, constants. + +### Cross-references (XREF) + +Right-click any function / address → `References → Find References to`. Shows every place that calls it. Walk the call graph backward from interesting functions. + +--- + +## Making the decompiler's output readable + +Ghidra's decompiler is good but needs hints. These three actions dramatically improve its output: + +### 1. Rename variables + +Click a variable in the Decompiler view → press `L` → type a better name. Ghidra propagates the rename across all uses. + +### 2. Set types + +A variable that looks like `undefined4` or `void *` is unhelpful. Click it → press `Ctrl+L` → set type (e.g. `int`, `char *`, `struct my_header *`). + +For pointers to structs from headers you have, use: +``` +File → Parse C Source → paste header file → auto-creates struct types +``` + +Then assign the struct type to the pointer. Ghidra resolves field accesses immediately. + +### 3. Retype function signatures + +Click the function name in the Decompiler → press `F` (Edit Function Signature) → set return type + argument types. This propagates through callers. + +Do these three actions on the 3-5 most relevant functions and the decompiler output becomes near-source-readable. + +--- + +## Patterns for specific bug types + +### Looking for integer overflow / buffer overflow + +``` +Listing: look for + - LEA → CMP patterns on sizes + - memcpy/strcpy/sprintf with non-constant sizes +Decompiler: look for + - arithmetic on size_t without bounds check + - `+ user_input` in a length calculation +``` + +### Looking for a missing auth check + +Navigate from the handler entry (found via Strings or Imports) and check the control flow: + +``` +Decompiler: does the function return early / jump to error handler when some flag is not set? + If the check is absent, that's the bug. +``` + +### Looking for hardcoded URLs / keys / paths + +``` +Search → For Strings → filter `http:` / `https:` / `/etc/` / `bearer ` / `api_key` +``` + +### Looking for dispatch / plugin loading + +``` +Imports → dlopen, LoadLibrary, dlsym, GetProcAddress +``` + +The strings referenced near those calls are often plugin names. + +--- + +## Scripting (headless Ghidra) + +When you need to automate analysis across many binaries, or repeat a workflow: + +```bash +# Headless analyzer +$GHIDRA_INSTALL_DIR/support/analyzeHeadless \ + \ + -import \ + -postScript +``` + +Ghidra supports Python 3 scripts (via Jython-compatible API) and Java. Useful scripts: + +- Dump all function signatures to JSON +- Find all calls to `system()` with constant arguments +- Auto-rename FUN_xxx based on heuristics (string refs, call patterns) + +The community has a large collection: https://github.com/NationalSecurityAgency/ghidra/tree/master/Ghidra/Features/Base/ghidra_scripts + +--- + +## Bookmarks + Notes + +Ghidra has built-in bookmarks and comments. Use them as your journal inside the project: + +- Right-click an address → `Set Bookmark` → tag as `Note`. Attach a description. +- Right-click → `Comments → Set EOL Comment` (shows up inline in decompiler). + +Treat these as part of the journal. If you end up promoting this Ghidra project (keeping it after the debug session), the comments become durable documentation. + +--- + +## Gotchas + +- **Large binaries need more Java heap.** Edit `support/launch.properties` and bump `VMARGS=-Xmx8G` (default is often 2G, too small). +- **Save often.** Ghidra's autosave is not instant; a crash loses uncommitted analysis. +- **Decompiler has timeouts.** For complex functions it may give up and print `/* WARNING: ... */`. Increase timeout in `Edit → Tool Options → Decompiler`. +- **Archs beyond x86/ARM/MIPS** sometimes need Sleigh processor module tweaks. Rare but possible. +- **Signed vs unsigned decompilation** is frequently wrong. Manually retype when integer behavior matters. + +--- + +## When Ghidra is NOT the right tool + +- You have the source. Go read it. +- Bug is in your own recently-compiled binary. Rebuild with `-g` and use gdb/pwndbg (see [pwndbg.md](pwndbg.md)). +- You just need to know which libs a binary links. `ldd` / `otool -L` / `readelf -d` are faster. +- You just need strings. `strings -n 8` is faster. + +Ghidra is the right tool when you need to **read the logic** of a binary you don't have source for. + +--- + +## Phase 9 cleanup specifics + +```bash +# If you created a scratch project just for this debug session, journal path and remove: +# (the journal should have the exact path) +# Example: +ls ~/ghidra-projects/ 2>/dev/null +# rm -rf ~/ghidra-projects/debug- + +# If you promoted the project (kept it), it's not cleanup — note it in the final summary to the user + +# Kill any running Ghidra headless processes +pkill -f 'analyzeHeadless' || true +``` diff --git a/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md b/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md new file mode 100644 index 000000000..7d6367156 --- /dev/null +++ b/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md @@ -0,0 +1,194 @@ +# Playwright CLI — Browser QA That Actually Drives a Browser + +**https://playwright.dev/ · https://github.com/microsoft/playwright** + +For any browser-served web UI bug, this is the correct tool. Not curl. Not imagination. Not a headless HTTP library. A real browser with a real rendering engine, real JS execution, real cookies, real service workers, real viewport. + +**In Phase 8 Manual QA for browser products, using Playwright is not optional.** Curl cannot catch: CSS that breaks at specific viewport widths, hydration mismatches, client-side router bugs, cookie/session interactions, service-worker caching, JS-triggered navigations. All of those are common bug classes. Drive a browser. + +> Note: `microsoft/playwright-cli` is the legacy repo; the current tooling lives in `@playwright/test` (npm) and `playwright` (pip), which include the `playwright` CLI. Use those — the legacy `playwright-cli` package is deprecated. + +--- + +## When to reach for Playwright + +| Bug symptom | Use Playwright? | +|---|---| +| Form submit produces wrong result | ✅ — Playwright drives the form exactly as a user does | +| Page blank in prod, fine locally | ✅ — hydration/env differences need a real browser | +| CSS looks wrong at a specific width | ✅ — use `--viewport-size` | +| Click doesn't fire / wrong handler | ✅ — Playwright fires real DOM events | +| Flash of unstyled content / loading glitch | ✅ — use trace viewer to see frames | +| API returns wrong data | ❌ — use curl, this isn't a browser bug | +| Backend returns wrong status code | ❌ — use curl | +| Client hits a URL that returns 500 | ✅ but also ❌ — Playwright shows the call + response + failure effect on UI | + +--- + +## Install (per-project) + +Playwright installs browser binaries separately from the npm package. + +```bash +# In the project +npm init playwright@latest # interactive; picks TS/JS + browsers + config +# Or if Playwright is already a dep: +npx playwright install # downloads browsers +npx playwright install chromium # just chromium +npx playwright install --with-deps # also installs OS deps (Linux) +``` + +Python: +```bash +pip install playwright +playwright install +``` + +--- + +## The four things you'll actually use + +### 1. `codegen` — record a session, generate the script + +The fastest way to create a repro. Opens a real browser; your clicks / typing become a Playwright script you can paste into a test. + +```bash +npx playwright codegen https://your-app.local +npx playwright codegen --viewport-size=375,667 https://your-app.local # iPhone SE size +npx playwright codegen --device="iPhone 14" https://your-app.local +``` + +Click / type / navigate in the browser; watch the script build in the side panel. Copy the generated script into your journal as the repro for Phase 8. + +### 2. A one-shot Playwright script — reproduce + capture + +Usually the Phase 8 QA artifact. Save to `/tmp/debug-repro.spec.ts` (journal it): + +```ts +// /tmp/debug-repro.spec.ts +import { test, expect } from '@playwright/test'; + +test('refinement chat shows non-empty response when env var set', async ({ page }) => { + await page.goto('http://localhost:3000/chat'); + await page.fill('textarea[name="message"]', 'Add a logging step'); + await page.click('button[type=submit]'); + + // Wait for the response to appear (not just the spinner to disappear) + const response = page.locator('[data-testid="assistant-reply"]'); + await expect(response).toBeVisible({ timeout: 30_000 }); + await expect(response).not.toBeEmpty(); + + // Capture evidence + await page.screenshot({ path: '/tmp/debug-after-fix.png', fullPage: true }); + console.log(await response.textContent()); +}); +``` + +Run it with tracing enabled for rich post-mortem: + +```bash +npx playwright test /tmp/debug-repro.spec.ts --trace on --headed +``` + +### 3. `PWDEBUG=1` — step through the script with Playwright Inspector + +```bash +PWDEBUG=1 npx playwright test /tmp/debug-repro.spec.ts +``` + +Opens the Playwright Inspector alongside the browser. You can step through Playwright actions, see the DOM state at each step, and edit selectors on the fly. + +Use this when the script doesn't reproduce cleanly and you need to watch it run. + +### 4. `show-trace` — post-mortem on a failed run + +```bash +npx playwright show-trace trace.zip +# or from the test-results dir: +npx playwright show-trace test-results//trace.zip +``` + +Scrubs through a recorded session: timeline, DOM snapshot at each action, network, console, source. When a test failed on CI but passed locally, this is the single best artifact. + +--- + +## Headless vs headed during debugging + +Always add `--headed` when debugging. Headless browsers sometimes behave subtly differently (font rendering, viewport, media permissions). For QA evidence, run headed and screenshot. + +```bash +npx playwright test --headed +npx playwright test --headed --project=chromium # pin the browser +``` + +--- + +## Catching the silent-failure patterns Playwright is good at + +```ts +// Toast that flashes and disappears +page.on('console', msg => console.log('[browser console]', msg.type(), msg.text())); + +// Unhandled page errors (uncaught exceptions in the page JS) +page.on('pageerror', err => console.error('[page error]', err)); + +// Network failures — e.g., backend returned 500 but UI shows nothing +page.on('response', async resp => { + if (!resp.ok()) { + console.warn(`[network ${resp.status()}] ${resp.url()} — ${await resp.text()}`); + } +}); + +// Request that never came back +page.on('requestfailed', req => { + console.error('[request failed]', req.url(), req.failure()?.errorText); +}); +``` + +Add these listeners to the top of the debug script. They surface a lot of the "UI showed nothing" class of bug. + +--- + +## Viewport and device emulation + +CSS bugs that only appear at specific sizes, or layout bugs on mobile: + +```ts +// At test level +test.use({ viewport: { width: 375, height: 667 } }); + +// Per-page +await page.setViewportSize({ width: 375, height: 667 }); + +// Predefined devices +import { devices } from '@playwright/test'; +test.use({ ...devices['iPhone 14'] }); +``` + +--- + +## Gotchas + +- **Wait for state, not for time.** `await page.waitForTimeout(2000)` is flaky. Use `await expect(locator).toBeVisible()` or `page.waitForResponse(urlPattern)`. +- **Stale selectors re-resolve.** Playwright's locators re-find the element on each action, unlike Puppeteer's handles. Don't over-think it. +- **Service workers persist across test runs in headed mode.** If you see cached behavior from a previous run, add `await context.clearCookies()` + clear storage before the test. +- **Installing on CI requires `--with-deps`** on Linux images that lack the browser's shared-library deps. +- **Parallel tests share a browser process by default**; if one test polls a debugger port, others may interfere. Use `workers: 1` for debugging. + +--- + +## Phase 9 cleanup specifics + +```bash +# Remove trace files from debug runs +rm -rf playwright-report/ test-results/ trace.zip + +# Remove debug spec files from /tmp +rm -f /tmp/debug-*.spec.ts + +# Remove screenshot captures +rm -f /tmp/debug-*.png + +# If you installed browsers just for this session (rare): +# Don't remove them — they're useful for future sessions. They live in ~/Library/Caches/ms-playwright (macOS) or ~/.cache/ms-playwright (Linux). +``` diff --git a/packages/shared-skills/skills/debugging/references/tools/pwndbg.md b/packages/shared-skills/skills/debugging/references/tools/pwndbg.md new file mode 100644 index 000000000..8a28ae3ff --- /dev/null +++ b/packages/shared-skills/skills/debugging/references/tools/pwndbg.md @@ -0,0 +1,263 @@ +# pwndbg — GDB With the Useful Views Always On + +**https://github.com/pwndbg/pwndbg** + +pwndbg is a GDB plugin that turns GDB into something humans can actually use for binary debugging. It's strictly a superset of plain GDB — every vanilla GDB command still works, and pwndbg adds views and commands that make you productive. + +**If you'd reach for plain `gdb`, reach for pwndbg instead.** The only reason not to is if pwndbg isn't installed on the machine, and that's a 2-minute fix. + +--- + +## Install + +```bash +# macOS +brew install pwndbg +# Or from source: +git clone https://github.com/pwndbg/pwndbg +cd pwndbg && ./setup.sh + +# Linux +# Most distros: apt/dnf/pacman install pwndbg (check availability) +# Or the same git + ./setup.sh + +# Verify +gdb --version +gdb ./any-binary +# At gdb prompt, you should see pwndbg banner + colorful context view +``` + +Once installed, pwndbg auto-loads every time you start `gdb`. You don't source anything manually. + +--- + +## The `context` view — the one feature that changes everything + +Plain GDB: you run `info registers`, then `bt`, then `x/10xw $rsp`, then `disas`. Four commands to see what's going on. + +pwndbg: `context` (or it auto-shows at every break). One command. Everything on screen: + +``` +──── registers ──── + RAX 0x0 + RBX 0x7ffffffde158 + RCX 0x7fffff7abf10 + ... +──── disasm ──── + ► 0x401234 mov rdi, rax + 0x401237 call 0x401190 + ... +──── stack ──── + 00:0000│ rsp 0x7ffffffde0a0 → 0x7fffff7c4000 + 01:0008│ 0x7ffffffde0a8 → 0x0 + ... +──── backtrace ──── + ► f 0 0x401234 parse_input+0x3c + f 1 0x401180 main+0x120 + f 2 0x7fffff7a5083 __libc_start_main+0xf3 +``` + +You always know where you are, what the CPU state is, what's on the stack, and how you got here. This is why pwndbg is the default. + +--- + +## Launch recipes + +```bash +# Debug an existing binary +gdb ./target + +# With args +gdb --args ./target arg1 arg2 + +# Attach to a running process +gdb -p $(pgrep target) + +# With a core dump +gdb ./target ./core + +# Headless / remote (for automation or IDE attach) +gdbserver :2345 ./target # on the target box +gdb ./target # on your box +(gdb) target remote :2345 +``` + +At the pwndbg prompt: + +--- + +## Essential commands (pwndbg additions) + +### Layout / view + +``` +context # reprint the context view (usually auto) +context regs stack # only show registers + stack sections +tel $rsp 20 # telescope — walk pointers at $rsp for 20 slots (KEY COMMAND) +tel $rdi 10 # walk pointers at $rdi (e.g. to dump a struct) +stack 20 # 20 entries of stack +vmmap # virtual memory map of the process +``` + +**`telescope` is pwndbg's killer command.** Given an address, it walks pointers recursively: +``` +00:0000│ 0x7ffd... → 0x601010 (heap) → 0x2a (unknown, i.e. a number 42) +01:0008│ 0x7ffd... → 0x7fff... (stack) → 'hello world' +``` +This single view resolves 80% of "what is at this address" questions. + +### Heap debugging + +``` +heap # overview of chunks +bins # tcache / fastbin / unsorted / smallbin / largebin state +malloc_chunk # inspect a specific chunk +find_fake_fast # (exploit context) find fake-fast overlap candidates +vis_heap_chunks # visualize heap layout +``` + +For use-after-free / double-free / heap overflow hypotheses, `heap` + `bins` is usually sufficient to see the corruption. + +### Exploitation-adjacent (useful for bug understanding too) + +``` +checksec # NX, PIE, RELRO, canary status +rop --grep 'pop rdi' # find ROP gadgets +nx # step over (aliased nicely) +ni # step over single instruction +si # step into single instruction +``` + +### Search + +``` +search -t byte 0x41 # find byte 0x41 anywhere in memory +search -t string "admin" # find string +search -p # find pointers to +``` + +--- + +## Standard GDB commands still work + +pwndbg doesn't replace GDB; it augments it. Everything you know still works: + +``` +break main # breakpoint at function +b *0x401234 # breakpoint at address +b file.c:42 # breakpoint at file:line +c # continue +n # next (source-level step over) +s # step (source-level step into) +finish # step out +info breakpoints # list breakpoints +delete # delete breakpoint +watch # break on write to variable +rwatch # break on read +awatch # break on access +p # print expression +p/x # print in hex +x/20xw # examine 20 words as hex +bt # backtrace +frame # switch frame +info registers # registers (but `context` is better) +disassemble # disasm a function +``` + +--- + +## Python scripting inside GDB + +pwndbg exposes a full Python API. Useful for automating observations across many breakpoints: + +```python +(gdb) python +import gdb +def on_break(): + frame = gdb.selected_frame() + pc = frame.read_register('pc') + print(f'hit at {hex(int(pc))}') + # Dump args, locals, anything +end +``` + +Or scripted runs from outside: +```bash +gdb -batch -ex 'source script.gdb' -ex 'run' ./target +``` + +--- + +## Common workflows by bug type + +### Segfault / crash + +```bash +gdb ./target +(gdb) run +# ... crash ... +(gdb) context # see the crash site +(gdb) bt # how did we get here? +(gdb) info registers # what state +(gdb) tel $rsp 20 # what's on the stack +``` + +### "Function returns wrong value" + +```bash +gdb ./target +(gdb) break +(gdb) run +# At breakpoint: +(gdb) finish # let it run to the return +# pwndbg shows RAX (return value) in context +``` + +### "Variable has unexpected value at point X" + +```bash +gdb ./target +(gdb) break +(gdb) run +# At breakpoint: +(gdb) p # its value +(gdb) watch # set a watchpoint — break when it changes +(gdb) c # continue; next stop is where it was modified +``` + +### "Memory corruption / heap bug" + +```bash +gdb ./target +(gdb) run +# Crash at free(): +(gdb) heap # heap state +(gdb) bins # bin state — often shows corruption here +(gdb) vis_heap_chunks # visualize +(gdb) malloc_chunk +``` + +--- + +## Gotchas + +- **`bt` looks weird on stripped binaries** — function names become offsets. Use Ghidra's function labels to map back (see [ghidra.md](ghidra.md)). +- **PIE binaries have randomized base addresses.** Addresses you see in Ghidra are unslid; addresses in pwndbg are slid. The `vmmap` command shows the base, and pwndbg's `piebase` command gives you the offset. +- **Optimized builds inline functions.** You'll set a breakpoint on `my_function` and it won't hit because the function was inlined. Either disable optimizations or break on callers. +- **Stack canaries trigger `__stack_chk_fail`.** If you see that in a backtrace, the bug caused a stack-smash; look one frame up. + +--- + +## Phase 9 cleanup specifics + +```bash +# Kill gdb / pwndbg sessions +pkill -f 'gdb' || true +pkill -f 'gdbserver' || true + +# Remove core dumps generated during session +rm -f ./core ./core.* ~/core.* + +# Remove any scripted GDB files +rm -f /tmp/debug-*.gdb +``` diff --git a/packages/shared-skills/skills/debugging/references/tools/pwntools.md b/packages/shared-skills/skills/debugging/references/tools/pwntools.md new file mode 100644 index 000000000..e4b1e2dc1 --- /dev/null +++ b/packages/shared-skills/skills/debugging/references/tools/pwntools.md @@ -0,0 +1,265 @@ +# pwntools — Scripted Binary / Network Interaction + +**https://docs.pwntools.com/en/stable/ · https://github.com/Gallopsled/pwntools** + +pwntools is a Python framework for building reproducible interactions with binaries and network services. Originally built for CTF exploitation, it's the correct tool for any situation where you need: + +- A crafted input sent to a binary or network service, repeatably +- A "failing test" equivalent for a bug that only manifests with specific byte-level input +- A fuzz harness +- An exploit PoC +- Anything where you're tempted to use `echo ... | ./binary` but need more control than shell allows + +**Use pwntools for Phase 5 reproduction of binary bugs and Phase 7 tests against binaries.** + +--- + +## Install + +```bash +pip install pwntools +# Or in a venv: +python -m venv .venv && source .venv/bin/activate +pip install pwntools + +# Verify +python -c 'from pwn import *; print("ok")' +``` + +On some Linux distros you may need build deps: `apt install python3-dev libssl-dev`. + +--- + +## The core API in five idioms + +### 1. Process / Remote — the same interface + +```python +from pwn import * + +# Local process +p = process('./target') + +# Remote service +p = remote('example.com', 1337) + +# SSH (tunnel to a remote process) +shell = ssh('user', 'host', password='...') +p = shell.process('./target', cwd='/tmp') + +# Same methods on all of the above — this is the value proposition +``` + +### 2. I/O — the only five methods you need + +```python +p.send(b'data') # send bytes +p.sendline(b'data') # send bytes + \n +p.recv(n) # receive up to n bytes +p.recvuntil(b'> ') # receive until pattern (blocks) +p.recvline() # receive until \n +p.interactive() # hand control to your terminal (for manual exploration) + +# Combined +p.sendlineafter(b'prompt> ', b'payload') +p.sendafter(b'key:', key) +``` + +Timeouts: +```python +try: + data = p.recvuntil(b'done', timeout=5) +except pwnlib.exception.EOFError: + print('process died') +except TimeoutError: + print('no response in 5s') +``` + +### 3. context — set arch/OS once, tools align + +```python +context.binary = elf = ELF('./target') # auto-sets arch/os/endianness +# or explicitly: +context.update(arch='amd64', os='linux', endian='little', bits=64) +``` + +After setting context, helpers like `asm()`, `disasm()`, `cyclic()`, and `ROP()` produce correct output for that target automatically. + +### 4. ELF — parse without reverse-engineering by hand + +```python +elf = ELF('./target') + +elf.symbols['main'] # address of main +elf.plt['printf'] # address in PLT (dynamic linkage) +elf.got['printf'] # GOT entry +elf.address = 0x555555554000 # set base for PIE binaries +elf.search(b'/bin/sh') # find string or bytes in the binary +elf.functions['main'].address # same as elf.symbols['main'] +list(elf.functions)[:10] # first 10 function names +``` + +For the libc that's linked: +```python +libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') +libc.symbols['system'] +``` + +### 5. cyclic — find offsets without counting + +For "where exactly does user input reach this variable" bugs: + +```python +p = process('./target') +p.sendline(cyclic(256)) # send a De Bruijn pattern +# Crash occurs; note the crash value (e.g. RIP = 0x6161616c) +offset = cyclic_find(0x6161616c) # returns 12 (or wherever in the pattern) +# Now you know: byte 12 of your input lands at RIP +``` + +Saves an hour of "pad by N bytes then check" iteration. + +--- + +## Logging during debug + +pwntools logs output by default. Configure level in the script: + +```python +context.log_level = 'debug' # very verbose — shows sent/received bytes +context.log_level = 'info' # default +context.log_level = 'warning' # quiet +``` + +For long scripts, log milestones: + +```python +log.info('Connected to target') +log.success('Bypassed the check') +log.failure('Canary corrupted') +log.progress('brute-forcing').status('attempt %d' % i) +``` + +--- + +## Typical debug-session patterns + +### Reproduce a crash with a specific input + +```python +# /tmp/debug-repro.py +from pwn import * +context.binary = './target' + +p = process('./target') +p.sendlineafter(b'> ', b'') +p.wait() +# If it crashed, p.poll() returns non-zero +assert p.poll() is not None and p.poll() != 0, 'expected crash, got clean exit' +log.success(f'confirmed crash (exit {p.poll()})') +``` + +Journal this script path. Run it as your "red test": + +```bash +python /tmp/debug-repro.py +``` + +### Fuzz harness for a suspected input class + +```python +# /tmp/debug-fuzz.py +from pwn import * +import random + +context.binary = './target' +context.log_level = 'warning' # keep quiet in the loop + +crashes = [] +for i in range(1000): + payload = bytes(random.randint(0, 255) for _ in range(random.randint(1, 100))) + p = process('./target') + p.sendline(payload) + p.wait() + if p.poll() is not None and p.poll() < 0: # crashed by signal + crashes.append((payload, p.poll())) + log.success(f'iter {i}: crash sig={-p.poll()}') + +open('/tmp/debug-crashes.txt', 'w').write(repr(crashes)) +log.info(f'found {len(crashes)} crashes') +``` + +### Automated exploit harness (CTF or self-testing a known CVE) + +```python +from pwn import * +context.binary = elf = ELF('./target') +libc = elf.libc or ELF('/lib/x86_64-linux-gnu/libc.so.6') + +p = process('./target') + +# Leak +p.sendline(b'A' * 64 + p64(elf.plt['puts']) + p64(elf.symbols['main']) + p64(elf.got['puts'])) +leak = u64(p.recv(6).ljust(8, b'\x00')) +libc.address = leak - libc.symbols['puts'] +log.success(f'libc base: {hex(libc.address)}') + +# Exploit +rop = ROP(libc) +rop.system(next(libc.search(b'/bin/sh'))) +p.sendline(b'A' * 64 + rop.chain()) + +p.interactive() +``` + +--- + +## Integration with gdb / pwndbg + +pwntools can launch your process under gdb: + +```python +p = gdb.debug('./target', gdbscript=''' + break main + continue +''') +``` + +Or attach to a running pwntools-launched process: + +```python +p = process('./target') +gdb.attach(p, gdbscript='break *0x401234') +# continues in a new terminal window with gdb attached +p.sendline(b'trigger input') +``` + +This is the best way to debug a specific crash repeatably — pwntools drives input, gdb/pwndbg observes runtime state. + +--- + +## Gotchas + +- **Python version**: pwntools supports Python 3.8+. Very old distros may not have it. +- **`p.interactive()` blocks.** It's for manual exploration; remove it from automated scripts. +- **ASLR on local runs**: turn off for reproducibility during debugging: `echo 0 | sudo tee /proc/sys/kernel/randomize_va_space` (remember to revert — journal this!). +- **`gdb.debug()` requires `gdb-multiarch`** for cross-arch binaries. +- **Subprocess cleanup**: if your script crashes, orphan `./target` processes may linger. Kill them at Phase 9 or add `atexit` cleanup. + +--- + +## Phase 9 cleanup specifics + +```bash +# Remove pwntools debug scripts +rm -f /tmp/debug-*.py +rm -f /tmp/debug-crashes.txt + +# Kill orphan target processes from failed runs +pkill -f './target' || true # adjust to actual binary name + +# Restore ASLR if disabled +# echo 2 | sudo tee /proc/sys/kernel/randomize_va_space # Linux default + +# Revert any binary patches applied for testing (see native-binary.md for details) +```