takeway
1 | takeway: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=eca4ff2062c2289a398fdd0099d226072093950f, for GNU/Linux 3.2.0, stripped |
- 64位,dynamically,Partial RELRO,Canary,NX
漏洞分析
UAF 漏洞:
1 | printf("Please input your order index: "); |
入侵思路
程序限制了申请块的数目:
1 | if ( index >= numg || (index & 0x80000000) != 0 || chunk_list[index] ) |
由于程序没有开 PIE,因此我们可以直接申请 numg 所在的空间,并修改 numg
最后直接申请到 GOT 表,完成泄露并且修改 dl_runtime_resolve
为 one_gadget 即可
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |
heapSpary
1 | GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35. |
1 | main: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=efe84eed376bd21cda8887c5de4e43ec76f723ac, for GNU/Linux 3.2.0, stripped |
- 32位,dynamically,全开
漏洞分析
程序可以执行函数指针:
1 | if ( index <= 0xFFF && chunk_list[index].data ) |
程序的写入没有限制字节数,可以无限堆溢出:
1 | puts("Please input your head data."); |
申请模块中允许的 size 范围非常大,可以调用 mmap 进行申请:
1 | if ( space > 0 && space <= 0x20000 ) |
入侵思路
首先程序有一个小点需要注意:
1 | puts("Please input your head data."); |
- 在读取函数
reads
中会对字符串末尾置零,而后续写入地址的操作会将这个\x00
给覆盖掉
利用这个特性可以轻松泄露程序基地址:
1 | add(0x28,"a"*0x28) |
用类似的方法可以泄露 libc_base:
1 | add(0x200,"a"*1) |
但如果想要在泄露 libc_base 的同时泄露 heap_base 的话,则需要一点技巧:
1 | def show(index): |
接下来的操作就有点玄学了,我们先看一下后门函数的逻辑:
1 | if ( index <= 0xFFF && chunk_list[index].data ) |
- func 地址位于该 chunk 的末尾(就是写入函数指针的位置)
- 两次解引用后数值必须为“0”,如果数值“0”后是 system 就可以拿到 flag
由于程序的申请操作受随机数影响,导致堆风水很乱,到这里时就只能尽可能多的写入 system 和 system 所在堆地址以提升打通的概率
- PS:有点像打内核题时用的堆喷技巧(kernel 的 slab/slub 对释放块有随机化保护,我猜题目也是为了模拟这一点)
完整 exp 如下:
1 | # -*- coding:utf-8 -*- |