我们按照学习原理,然后找一道例题学习,最后复盘总结的方式来学习这个知识点。
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,我们更多的是与其他的技巧结合的方式,做题之前得多次调试理解题目的逻辑和内存的构造,这样做题目才能够得心应手。
Github上公布的项目,项目地址 在网站上输入前段文本、加密文本、后段文本,点加密,即可天生一段由前段+后段文字的代码,看上去就是正常的文字,但其中却隐藏着信息。 复制这段代码,到下方网站即可解...
互联网30年,改变了整个世界,更改变了我们所有人的生活。一张互联网将全球70亿人连在了一起。从今天来看,这种连接才刚刚开始,随着2020年5G逐渐部署之后,预计要将7万亿个设备、500亿字节数据和...
目前,滚筒洗衣机比波轮式洗衣机要好用的多,因此也受到广大消费者的亲睐。滚筒洗衣机什么牌子好呢?人们在选择时候可能有点不知所措,今天我们就一起来看看最新滚筒洗衣机排行榜 最新滚筒洗衣机排行榜 1.海...
2020年全新百度百家引流方法专业技能 百度百家引流方法的使用价值 百度百家的引流方法使用价值小结为三高: 內容使用价值高。百度百家的客户以喜爱创作及其喜爱阅读文章的人群为主导,这一层...
本文目录一览: 1、因供应商遭受网络攻击 丰田宣布明日起在日16家工厂停产 2、超过200万辆奔驰曝出安全漏洞:智能汽车如何抵御黑客攻击? 3、同为2.0L+CVT,逍客和锋兰达谁更值得买?...
求黑客帝国2里墨菲斯在锡安演讲的英文台词 当我在母体时,我曾经仔细研究过你们人类的历史,我很愿意和你交流一下我研究的心得:我发现你们人类并非哺乳动物。任何哺乳动物都会努力保持自身和自然的平衡,而你们人...