堆中的off-by-one
pwn学到现在,觉得堆这一部分学的还不是很好,复习一下吧,再打打基础,别社我就行。
简介
off-by-one通俗易懂就是溢出一个字节,对边界检查的不够仔细,在pwn题中比较常见,一般在堆中出现的最多。
形成原理
1 |
|
可以看出第5行的for的判断出现了溢出,溢出一个字节i = 0; i <=size
而size为16,一共循环了17次,超出了原本想循环的16次,导致了chunk1溢出了一个字节,这就是off-by-one
字符串长度判断错误也会出现溢出,最典型的是strcpy和strlen
C 库函数 size_t strlen(const char *str) 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符
C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest。需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。
字符串检测到\x00时会停止录入,当strcpy进行拷贝时,会将\x00存入到堆中,就会将低字节覆盖成\x00,溢出一个字节
ctf的pwn题中以下的情况也是很常见的
1 |
|
第9行里面加了一个
a[i] = 2
将最后一个给覆盖成了2,理解一下吧,忘记有关题型被我放到哪里了。有些pwn堆题off-by-one中,全部输入完成之后最后确出现了heap[i] = 0
这种情况,溢出一个字节
实战
off-by-one中有很多典型题目,以asis2016_b00ks为例学习一下,有什么不对的地方欢迎指正。
直接查看保护
Full RELRO看到这个直接想到修改hook
直接拖到IDA
直接开始分析
creat
这个功能so easy,输入四个内容:书名大小、书名、书类型大小、书类型
直接开始创建exp的自动化creat函数
1 | def creat(name_size,name,content_size,content): |
delete
这个功能一看很遗憾,没有UAF。free之后的指针给清0了直接开始创建delete自动化函数
1 | def delete(index): |
edit
这个功能没什么好看的,也没有什么漏洞,直接上自动化函数
1 | def edit(index,content): |
show
这个功能很好啊,可以直接泄漏。(一些没有show函数,有沙盒的pwn题比这些有show的难多了
直接自动化函数
1 | def show(): |
change_author
在IDA里面一看
off_202018存放的是作者名,跟进off_202018,就可以发现在上8个字节里是off_202010(存入书的结构体)
这个sub_9F5函数很独特一眼就看见了,直接跟进。这不就是上面说的输入完成之后的heap[i] = 0?有关题型原来就是这个
因为202018与202010联系在一起,那么写32个字符,最后的\x00会溢出到202010的第一个字节
第17行的off-by-one很明显
上函数
1 | def change(author_name): |
漏洞利用思路
第一次创建作者或者修改作者名字时,如果填写32个字节的任意字符串,会导致\x00溢出到202018的低位,printf输出时会带着低位一起输出,借此输出book1_addr,book2_name,计算出libc_base,修改free_hook为gadget从而getshell
exp编写
第一次创建作者填满32个字节,进行一次off-by-one,之所以+’b’是为了能够更好的获取addr,下图中的红色圈为第33次写入的\x00
1 | r.sendlineafter('name: ','a'*0x1f+'b') |
这时我们创建图书,下面红色线为图书结构体
1 | creat(0xd0,'aaaaaaaa',0x20,'bbbbbbbb') |
这个时候可以show一下,因为之前溢出的\x00被覆盖成30,printf打印时会输出\x00,此时就会输出book1的结构体地址
1 | show() |
可以看到author的后面输出了book1的结构地址,直接接收
1 | r.recvuntil('aaaab') |
book1_addr被获取到了,接下来直接开始获取libc_base,创建book2,name大小为0x80,为之后的unstroted bin做准备,再创建一个book3
1 | creat(0x80,'cccccccc',0x60,'dddddddd') |
接下来释放book2形成unstorted bin,并且修改book1的name,content,然后更改作者名,将book1的低地址覆盖成\x00
1 | delete(2) |
让book1_name合法的指向unstorted bin的main_arena+88
直接show一下,就可以看到book1的name已经变成了main_arena+88,直接接收并计算libc_base
1 | show() |
libc_base出来之后,system bin_sh都不是事
修改free_hook为system
1 | edit(1, p64(free_hook) + p64(0x8)) |
最后创建binsh堆块,因为free_hook变成了system,释放bin_sh的堆块会成功执行system(‘/bin/sh’);
1 | creat(0x100, '/bin/sh\x00', 0x100, '/bin/sh\x00') |
直接python3 exp.py打的本地,远程99.99%的打的通,远程打不通那就是天意
exp
1 | from pwn import * |