我们按照学习原理,然后找一道例题学习,最后复盘总结的方式来学习这个知识点。
off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。
环境:pwndocker配置:
# 配置libc的版本 cp /glibc/2.23/64/lib/ld-2.23.so /tmp/ld-2.23.so patchelf --set-interpreter /tmp/ld-2.23.so https://www.freebuf.com/articles/web/b00ks LD_PRELOAD=/glibc/2.23/64/lib/libc.so.6 # 转发输入输出流到 10001 端口 socat tcp-listen:10001,reuseaddr,fork EXEC:https://www.freebuf.com/articles/web/b00ks,pty,raw,echo=0
按照惯例,我们先大概浏览一下题目,题目是一个图书管理系统:
1. Create a book 2. Delete a book 3. Edit a book 4. Print book detail 5. Change current author name 6. Exit
book 结构中存在 name 和 description , name 和 description 在堆上分配。首先分配 name buffer ,使用 malloc ,大小自定但小于 32 。
printf(" Enter book name size: ", *(_QWORD *)&size); __isoc99_scanf("%d", &size); printf("Enter book name (Max 32 chars): ", &size); ptr=malloc(size);
之后分配 description ,同样大小自定但无限制。
printf(" Enter book description size: ", *(_QWORD *)&size); __isoc99_scanf("%d", &size); v5=malloc(size);
之后分配 book 结构的内存
book=malloc(0x20uLL); if ( book ) { *((_DWORD *)book + 6)=size; *((_QWORD *)off_202010 + v2)=book; *((_QWORD *)book + 2)=description; *((_QWORD *)book + 1)=name; *(_DWORD *)book=++unk_202024; return 0LL; }
综合调试,我们得出book的结构体如下:
struct book{ int id; char *name; char *description; int size; }
漏洞点
程序编写的 read 函数存在 null byte off-by-one 漏洞,仔细观察这个 read 函数可以发现对于边界的考虑是不当的。
signed __int64 __fastcall MyRead(char *a_string, int str_size) { int i; // [rsp+14h] [rbp-Ch] char *buf; // [rsp+18h] [rbp-8h] if ( str_size <=0 ) return 0LL; buf=a_string; for ( i=0; ; ++i ) { if ( (unsigned int)read(0, buf, 1uLL) !=1 ) return 1LL; if ( *buf==10 ) break; ++buf; if ( i==str_size ) break; } *buf=0; return 0LL; }
攻击过程
1.填充满 author
2.创建堆块1,覆盖author结尾的\x00,这样我们输出的时候就可以泄露堆块1的地址
3.创建堆块2,为后续做准备,堆块2要申请得比较大,因为mmap申请出来的堆块地址与libc有固定的偏移
4.泄露堆块1地址,记为first_heap
5.在调试中我们知道,创建的book结构体的地址是存储在全局变量中的,该全局变量正好与author相临,更改 author,利用多写的一个\x00字节,可以覆盖到堆块1的地址的最后一位,如果我们提前将堆块1的description内容编辑并伪造一个book的结构体,然后多次调试构造好位置,让覆盖过后的地址刚好是book1的description部分的话,我们相当于获得了一个任意地址读写
6.任意读:获得libc地址
7.任意写:将__free_hook函数的地址改写成one_gadget地址
__free_hook若没有则不调用,若有将先于free函数调用
OK现在我们用IDA远程调试的方式按照上边的利用思路来调试一下:
填充 author
我们创建一个32字节大小的名字A
createname(b"A"*32)
然后我们看到填充的内存,除了32个A之外,还额外增加了一个\x00。
创建堆块1、创建堆块2
创建堆块1后,我们看到0x55C38D8C7530覆盖了author的\x00字节。创建的堆块2的地址也紧接着堆块1。
我们看看堆块1和堆块2的结构体如下:
泄露堆块1地址
我们通过上一步覆盖了末尾的\x00后,就可以打印出堆块1的地址,如下图:
编辑堆块1的description字段,伪造堆块1
Payload代码如下,我们注意到我们构造这个假堆块1,它的name指针0x55C38D8C7568指向的是堆块2的name,它的description指针0x55C38D8C7570指向的是堆块2的description。
payload=b'A'*0x50+p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x40)+p64(0xffff) editbook(book_id_1,payload)
让伪造的堆块1恰巧在地址0x55C38D8C7500处,有人或许会问为什么你构造的堆块1是恰巧在这个位置的,因为这是经过多次尝试和调试出来的,适当的将堆块1的description大小分配的大一些,然后增加Payload的偏移。
接下来,最关键的时候到了,如下图,我们再次修改author,使得author填充完32个A之后,还是将\x00覆盖了原始堆块1的地址,导致现在的地址变为了0x55C38D8C7500,恰好指向了我们构造的假的堆块1的地址,这样以来,我们就可以实现任意地址的读写。
获取libc地址
接下来,我们利用打印功能,打印出堆块1的相关值,因为mmap申请出来的堆块地址与libc有固定的偏移,所以我们利用前边多次调试得到的偏移,计算出libc。
将__free_hook函数的地址改写成one_gadget地址
首先使用editbook(1,p64(free_hook))将free_hook的指针写到堆块2的description变量中去,然后使用editbook(2,p64(one_gadget)),将one_gadget地址写入到free_hook中去。
最后我们再调用free获得shell
在刚开始调试的时候以为是使用pwntools+GDB的调试方式效率比较高一些,但最终发现,图形界面的IDA使得调试时候的视野更加广泛一些,而且还有强大的反编译功能,特别适合初学,也增加了学习的效率。此外针对off-by-one,我们更多的是与其他的技巧结合的方式,做题之前得多次调试理解题目的逻辑和内存的构造,这样做题目才能够得心应手。
腾冲旅游景点介绍,腾冲可能大家不是很熟悉,但腾冲确有很多标志性的景点,吸引着全国各地的游客前往,到底是那些景点那么的吸引人呢,腾冲旅游景点介绍带你玩转腾冲。 腾冲县位于云南省保山市西南部,西部与...
27届推新人展演辽宁盛典拉开大幕! 选手热情点燃“冬天里的一把火”! 27届推新人展演正在全国各省市火热进行,继盛京站、山东站相继启动后,“27届全国推新人展演”辽宁盛典新闻发布会暨小市一...
菜鸟学习初级教程—–强烈推荐(看完后成黑客拉) 第一篇 看完的人10个有9个成了黑客 看完的人10个有9个成了黑客 还有一个是BC 然而看完的人 视力全下降1度 黑客的基本技能 1、黑...
项目负责人岗位职责(项目经理职责及权限) 采用项目经理负责制,有绝对权利可以调配本工程现场人力、物力、财力、施工队和优先使用公司其他工程范畴的资源,保证工程保质保量按时完成。 其具体职责是: 1...
2019国有四大银行(工商银行,农业银行,建树银行,中国银行)存款利率如下:1、活期存款:0.3002、按期存款(零存整取):三个月:1.350 半年:1.550 一年:1。. 自2015年1...
0x01 一个waf的绕过进程 一个邮件软件NBC对Corenumb所反映的状况,敏捷给予了回应。 但到目前为止,还没有任何音讯可以标明该缝隙现已得到修正。 而能让我们感到欣喜的一点是,一些热心...