前言:
这几天请了一周的假回了一趟家,没有怎么花时间好好学习,就抽了空余的时间看了几眼,好在能把格式化字符串最后一点的利用方式给撸出来,还是蛮开心的,现在在候机室撸一下解析文章,还有一个半小时就上飞机了,应该来的及写完的吧。有空还想写一写这几天在家里的一些感想,想了蛮多的,还有各种复杂的感情得好好梳理梳理。
概述:
这里讲一下堆内存上面的格式化字符串利用方式,总体来说也就那几个点,学会用n修改地址就行,这里的话肯定会栈上不可执行,而且写入的东西在堆上面,所以这里利用方式就是把栈转移到堆上面来进行利用。
解析:
check一下之后发现:
开了canary和nx,栈上不可执行,运行之后发现是创建联系人,移除联系人,编辑联系人和显示联系人这几个功能。查看一下漏洞点:
1 | void __cdecl sub_8048BD1(int a1, int a2, int a3, char *format) |
print format处有格式化字符串的漏洞,再可以看到这块是显示联系人中的显示描述的部分。
在看输入描述的函数,看见输入描述并不在栈中,而是分配在堆内存之中。用gdb来调,这时候我们应该有一个总体的大致思路:
- 获取libc拿到system函数和/bin/sh字符串
- 获取到堆内存的存储地址
- 修改ebp,使栈迁移到堆上面,使得程序main返回时执行system函数
获取libc:
在格式化字符串漏洞处下断点:
到断点之后往栈中查看一下有没有可以打印地址的可泄漏的函数:
果然
看到了libc_start_main的函数,计算便宜之后得到偏移地址为31,所以在描述之中写上
1 | %31$p |
就能leak出它的地址,再减去247就是真正的libc地址,之后就好利用了。不过这里有一个坑点,这里用libcdatabase得到的偏移,system函数是正确的,但是/bin/sh却一直不正确,所以这里我把gdb attach上去之后用/bin/sh相对于system的偏移来计算了,好在确实正确:
1 | system_addr = base + libc.symbols['system'] |
获取堆内存:
这里也是很方便的:
我们这里只需要leak描述处的堆地址就好了,因为之后我们可以在描述部分写上system函数地址,控制程序流程之后可以在堆上执行,所以描述部分的偏移为11,只需要写上:
1 | %11$p |
leak出堆内存地址。
修改ebp:
我们可以控制的恰好是堆内存,所以我们可以把栈迁移到堆上去。这里我们通过leave指令来进行栈迁移,所以在迁移之前我们需要修改程序保存ebp的值为我们想要的值。 只有这样在执行leave指令的时候,esp才会成为我们想要的值。同时,因为我们是使用格式化字符串来进行修改,所以我们得知道保存ebp的地址为多少,而这时PrintInfo函数中存储ebp的地址每次都在变化,而我们也无法通过其他方法得知。但是,程序中压入栈中的ebp值其实保存的是上一个函数的保存ebp值的地址,所以我们可以修改其上层函数的保存的ebp的值,即上上层函数(即main函数)的ebp数值。这样当上层程序返回时,即实现了将栈迁移到堆的操作。
再清楚点:
图中的蓝色框就是这个显示函数所返回的ebp,蓝色框中所返回的ebp,即紫色框就是main函数的ebp,所以我们只需要将main函数的ebp给改成我们所构造的system_addr - 4 的地址就可以了。这里还需要知道%n和%s的用法差不多,改变的都是栈之中的地址所指向的地方的内容,所以我们只需要改动偏移地址为6的栈中的内容,就可以改变main函数的ebp,这里我们的利用方式就是:
1 | playload = system + 'aaaa' + bin_addr + %(duidizhi - 4 - 12) %6$p |
最后再选择选项5退出之后,这里执行playload之后所得到的ebp就在system函数的前四位,经过leave之后,esp就指在了system函数上,ret后最终执行system函数。
EXP:
1 | from pwn import * |