SUCTF 2019 部分pwn writeup
0x01 playfmt
程序分析
如图,32位的程序。没有开启PIE和canary。
拖进IDA中反编译看伪代码。main函数中做了一系列和堆相关的操作,但是do_fmt函数中的格式化字符串漏洞可以直接完成漏洞利用getshell,直接进入do_fmt函数,函数中存在明显的格式化字符串漏洞,而且这个是在**while(1)**中无限循环,直到输入“quit”才会return。格式化漏洞的具体漏洞原理请看最后的参考资料。
漏洞利用
在漏洞函数中的printf(buf)后下断点,查看一下栈的状态。
如图可以清晰的看到,格式化字符串的第7个参数是do_fmt函数的返回地址,而第6个参数中的指针指向offset为14的栈上,而offset为14的栈中存放着一个栈的地址。因此,利用思路为,用**%8$x泄露出libc的地址,用%6$x泄露出栈的地址,然后减法得出返回地址所在的栈地址,再用%*c$6hn将offset为14的栈上的地址的低二字节改写成返回地址所在的地址,然后就可以用%*c$14hn将返回地址的低2字节覆盖成system函数的低2字节,然后将offset为14的栈上的地址改写成返回地址所在的栈地址加2,就可以将返回地址的高2字节覆盖成system地址的高2字节**,就成功将返回地址覆盖成system函数的地址了。然后再用同样的方式,将libc中的“/bin/sh”字符串的地址写到返回地址的参数(offset为9)的栈上,输入“quit”触发return,即可getshell。
详细的操作请看以下exp,结合调试很容易明白。
完整EXP
1 | #-*-coding:utf8-*- |
参考资料
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_intro-zh/
https://www.anquanke.com/post/id/85785
0x02 二手破电脑
程序分析
如图,32位程序,保护全开
程序提供了4个功能,分别是:purchase、comment、throw、rename
main函数
如下图,main函数中会申请一个40字节的堆块,用于存放10个结构体指针。这个List指针作为参数传递给各个功能函数。
存在漏洞的purchase功能
1 | struc_1 **__cdecl purchase(struc_1 **List) |
其中,struc_1结构体定义如下:
读取name的函数:
红框处实际运行时就是scanf(“%s”, name),这里是存在空字节溢出的。当输入的字符串长度等于传递过来的size时,由于scanf函数的自动补0,会溢出一个空字节。
comment功能
可以看到,comment功能申请一个固定大小的堆块,往里读入comment字符串,然后向结构体的score成员变量读入一个整数。
throw功能
释放结构体和相关堆块,没有存在漏洞。
rename功能
rename功能是没有返回的,其中有一个magic函数。raname功能的操作比较骚气,看的出来是出题人用来实现漏洞利用并且加了一些限制的。但是我在漏洞利用的时候并没有用到这个功能,可能有点算非预期吧。由于我没有用到,这里就不赘述了。
漏洞利用
- 通过unsorted bin的双向链表来泄露出堆的地址和libc的地址。
- 32位下,如果申请的size是4字节对齐而非8字节对齐时,glibc的内存管理会使用内存复用,即下一个chunk的pre_size域同时作为当前chunk的数据域的最后八字节。因此我们可以通过内存复用和空字节溢出,覆盖到下一个chunk的size域,将pre_inuse置零,pre_inuse置零后,下一个chunk的pre_size域也会生效,因此,可以通过控制pre_size域伪造chunk,经过巧妙的构造造成堆块的堆叠。
- 通过off by null 空字节溢出漏洞,造成块堆叠。然后通过块堆叠将List中的某个结构体指针修改成List的指针,释放掉,就可以将List这个堆块申请出来,再通过它和comment功能中的读取整数score功能修改free_hook为system函数地址,free掉一个有**/bin/sh**的堆块即可get shell。
具体操作请看以下EXP,类似的漏洞利用原理可以参考RCTF2018的babyheap
完整EXP
1 | #-*-coding:utf8-*- |
End of article