offensive-tools/network/pwntools/SKILL.md
Python CTF/exploitation framework for interacting with remote services, local processes, and binary exploitation. Core use case: scripting interactive protocols over TCP/process tubes — recv/send loops, oracle interactions, multi-round crypto challenges, and shell exploitation. Use when writing a CTF solver that talks to a service, automating a multi-step protocol, or building binary exploitation payloads.
npx skillsauth add aeondave/malskill pwntoolsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Service-interaction and binary exploitation framework. The central abstraction is the tube — a uniform API for TCP sockets, local processes, and SSH channels. Write your solver once; switch between local and remote with one flag.
pip install pwntools
Every CTF solver follows this pattern:
from pwn import *
import sys
# Connection switch: python solver.py host:port
# Local: python solver.py → process(cmd)
# Remote: python solver.py host:1337 → remote(host, port)
# Debug: python solver.py host:1337 debug → remote(..., level='debug')
if len(sys.argv) > 1:
host, port = sys.argv[1].split(':')
level = sys.argv[2] if len(sys.argv) > 2 else 'info'
io = remote(host, int(port), level=level)
else:
io = process(['python3', 'challenge/server.py'])
# --- solver logic ---
io.close()
This template appears verbatim in HTB Cyber Apocalypse 2025 (Twin Oracles, Kewiri, Traces) and is the de-facto standard.
All receive calls block until the delimiter/count is met, or raise EOFError on close.
| Method | Description |
|--------|-------------|
| io.recv(n) | Up to n bytes, returns as soon as any data is available |
| io.recvn(n) | Exactly n bytes — blocks until complete |
| io.recvline() | Until \n — includes newline by default |
| io.recvline(drop=True) | Until \n — strips newline |
| io.recvuntil(b'delim') | Until delimiter — includes delimiter |
| io.recvuntil(b'delim', drop=True) | Until delimiter — strips it |
| io.recvregex(b'pattern') | Until regex match |
| io.recvregex(b'pattern', capture=True) | Returns re.Match object |
| io.recvall() | Until EOF |
| io.recvrepeat(timeout) | Until timeout or EOF |
| io.clean() | Drain buffer (discard pending data) |
# Read a line, strip newline, decode to str, extract last token
value = int(io.recvline().decode().strip().split()[-1])
# Read until marker, drop marker, decode, split on ' = '
value = int(io.recvuntil(b'\n', drop=True).decode().split(' = ')[1])
# Read multiple named values from consecutive lines
# Server sends: "n = 12345\ne = 65537\n"
n = int(io.recvline().decode().split(' = ')[1])
e = int(io.recvline().decode().split(' = ')[1])
# Receive until prompt, then parse all hex values from the output
raw = io.recvuntil(b'> ').decode()
import re
values = list(map(bytes.fromhex, re.findall(r'[0-9a-f]{6,}', raw)))
| Method | Description |
|--------|-------------|
| io.send(data) | Raw bytes, no newline |
| io.sendline(data) | Bytes + \n |
| io.sendafter(b'delim', data) | recvuntil(delim) then send(data) |
| io.sendlineafter(b'delim', data) | recvuntil(delim) then sendline(data) |
| io.sendlinethen(b'delim', data) | sendline(data) then recvuntil(delim) |
# Send integer as decimal string
io.sendline(str(value).encode())
# Send integer as hex string (no 0x prefix)
io.sendline(hex(value)[2:].encode())
# Send after prompt: recvuntil(b'> ') then sendline(answer)
io.sendlineafter(b'> ', str(answer).encode())
# Send comma-separated list
io.sendlineafter(b'> ', ','.join(map(str, my_list)).encode())
# Send underscore-separated factorization (HTB Kewiri pattern)
answer = '_'.join([f'{f},{e}' for f, e in factors])
io.sendlineafter(b' > ', answer.encode())
Server sends a prompt, client sends an answer, repeat.
for _ in range(100):
io.recvuntil(b'Question: ')
q = io.recvline(drop=True).decode()
answer = solve(q)
io.sendline(str(answer).encode())
flag = io.recvline().decode()
print(flag)
Server offers a menu. Client selects an option, sends ciphertext, receives result. Classic pattern in RSA/AES oracle challenges.
def query_oracle(ciphertext):
io.sendlineafter(b'> ', b'2') # select oracle option
io.recvuntil(b': ')
io.sendline(hex(ciphertext)[2:].encode())
return int(io.recvline().decode().split()[-1])
# Binary search using oracle (parity/LSB oracle)
lo, hi = 1, n - 1
for _ in range(n.bit_length()):
mid = (lo + hi) // 2
result = query_oracle(pow(2, e, n) * ciphertext % n)
if result:
lo = mid + 1
else:
hi = mid
Server requires: connect → auth/setup → challenge phase → flag retrieval.
# Phase 1: receive server parameters
io.recvuntil(b'n = ')
n = int(io.recvline())
io.recvuntil(b'e = ')
e = int(io.recvline())
# Phase 2: interact with challenge API
io.sendlineafter(b'option > ', b'1')
ct = int(io.recvline().decode().split()[-1])
# Phase 3: send solution
io.sendlineafter(b'answer > ', str(solution).encode())
flag = io.recvline().decode()
def interact(option, data=None):
io.sendlineafter(b'> ', str(option).encode())
if data is not None:
io.recvuntil(b': ')
io.sendline(data if isinstance(data, bytes) else str(data).encode())
return io.recvline().decode().strip()
# Use case
ct = interact(1) # option 1: encrypt
pt = interact(2, ct) # option 2: decrypt with ciphertext
# Service emits encrypted channel logs on connect
io.sendlineafter(b'> ', b'join #general')
raw = io.recvuntil(b'guest > ').strip().decode()
enc_data = list(map(bytes.fromhex, re.findall(r'[\da-f]{6,}', raw)))
context.log_level = 'debug' # show all send/recv bytes
context.log_level = 'info' # default — show sent/received sizes
context.log_level = 'warning' # suppress routine output
# Architecture (needed for ELF/shellcraft)
context.arch = 'amd64'
context.bits = 64
context.os = 'linux'
# Pass log level from command line
io = remote(host, port, level=sys.argv[2] if len(sys.argv) > 2 else 'info')
from pwn import *
# Pack integers to bytes (little-endian by default)
p64(0xdeadbeef) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0xdeadbeef) # → b'\xef\xbe\xad\xde'
u64(b'\xef\xbe\xad\xde\x00\x00\x00\x00') # → 0xdeadbeef
# XOR (operates on bytes, handles different lengths)
xor(b'key', b'plaintext')
xor(b'\x41\x42', b'\x01\x02') # → b'CB'
xor(ciphertext, key, plaintext) # three-way XOR
# Hex helpers
enhex(b'hello') # → '68656c6c6f'
unhex('68656c6c6f') # → b'hello'
# Number ↔ bytes (Crypto.Util.number equivalent)
from pwn import *
# pwntools does not ship long_to_bytes/bytes_to_long
# use pycryptodome for these:
from Crypto.Util.number import long_to_bytes, bytes_to_long
cyclic(100) # generate 100-byte De Bruijn sequence
cyclic_find(0x61616168) # find offset of 4-byte sequence
# Load ELF
elf = ELF('./vuln')
libc = ELF('./libc.so.6')
# Address lookup
elf.symbols['main']
elf.got['printf']
elf.plt['system']
# Leak libc base from a leak
libc.address = leaked_puts - libc.symbols['puts']
# ROP chain
rop = ROP(elf)
rop.call('system', [next(elf.search(b'/bin/sh\x00'))])
payload = flat({offset: rop.chain()})
# Shellcode
shellcode = asm(shellcraft.sh())
shellcode = asm(shellcraft.amd64.linux.sh())
# GDB attach (local process)
gdb.attach(io)
gdb.attach(io, gdbscript='b *main+0x42\ncontinue')
# Option 1: debug flag to process
io = process('./vuln', level='debug')
# Option 2: attach GDB mid-exploit
io = process('./vuln')
gdb.attach(io)
pause() # wait for gdb to attach before continuing
# Option 3: pause at specific point
io.sendline(b'A' * 40)
pause() # press enter to continue
# Option 4: inspect tube buffer state
io.unrecv(b'debug data') # push data back into recv buffer
io.recvline() when the service does not send a newline — use io.recvuntil(b'prompt') instead.int directly — always .encode() or wrap with str().drop=True on recvuntil when the delimiter should not be in the parsed value.io.recv() (non-blocking, may return partial) instead of io.recvn(n) when an exact byte count is needed.from Crypto.Util.number import long_to_bytes but assuming pwntools provides it — it does not.data-ai
Scoped routing: Linux operator; hosts, sessions, users, services, packages, logs, containers, SSH, network paths, privilege evidence.
development
Offensive methodology for ICS/OT/SCADA environments in authorized industrial penetration testing and red team operations. Use when assessing PLCs, RTUs, HMIs, engineering workstations, historians, or field devices running Modbus, DNP3, EtherNet/IP, S7comm/S7+, Profinet, IEC 60870-5-104, BACnet, or OPC-UA. Covers passive OT network enumeration, protocol-level device interrogation, PLC coil/register read-write attacks, HMI session exploitation, historian and engineering workstation compromise, and safe escalation rules for critical infrastructure scope. Does not cover: general IT network exploitation (network-technique), physical hardware interfaces UART/JTAG/SPI (hardware-technique), wireless sensor network attacks (wireless-technique), RF/SDR signal analysis (hardware-ctf or wireless-technique), or CTF-framed ICS lab tasks (ics-ctf).
tools
Offensive methodology for authorized game security assessments, game client security research, and game-adjacent penetration testing in real-world engagements. Use when assessing game clients for cheating vulnerabilities, testing anti-cheat effectiveness, auditing game server protocols for score manipulation or economic fraud, reverse engineering game DRM or license validation, analyzing game save file protection, or assessing game mod/plugin security. Covers: process memory scanning and manipulation (Cheat Engine methodology), game binary reversing for license and DRM bypass, game network protocol analysis and packet replay, anti-cheat mechanism analysis, save file format reversing and tampering, speed hack and value injection techniques. Does NOT cover: CTF game challenges (game-ctf), game engine source code auditing (web-exploit-technique or vuln-search-technique for the backend), or general binary exploitation (pwn-ctf or reversing-technique).
development
Auth assessment: hardware/embedded methodology; UART/JTAG/SWD/SPI/I2C, firmware extraction, boot/debug paths, embedded OS evidence.