虚拟PWN初探

前言

之前看到星盟Q群里面的消息,Freedom师傅在B站直播关于虚拟pwn入门的公开课,然后就去听了一波,感觉受益匪浅。之前一直以为虚拟pwn是超级复杂的东西,今年打比赛也遇到了好几次,一直无从下手。所以借着公开课学到的内容,复现了去年国赛虚拟pwn的那道题。这里写一篇博客记录下来,当作自己博客园的第一篇技术博文吧!主要是为了水周五的分享会

漏洞分析

就像Freedom师傅所说的,虚拟pwn的难点不是在于漏洞的利用,而是在于漏洞的分析,在于逆向分析能力。首先说明一点,这里我是直接在网上搜到了一个已经逆好的i64文件,自己跟着去逆向了一波。虽然感觉那位师傅逆的还是有点小瑕疵,但是u1s1,这么大的工程量,给我自己单独从头逆的话,真的逆不了这么清楚(留下了没技术的泪水)。
先在虚拟机运行一下:

从这里可以大致了解程序的功能,就是类似一个虚拟机一样执行我们输入的指令。
然后,直接拉进ida,找到main函数,f5反编译

可以看到,程序一开始便申请了三个段(利用堆申请出来的),一般虚拟pwn就是这三个段吧:代码段,栈段(运行栈),数据段(缓冲区),所以这个程序也不例外。(代码段主要用来放置我们的伪汇编指令,数据段放置我们一开始输入的数据,运行栈段就是执行add等这些指令时的栈段)
但是作为一个段的话,肯定会需要例如索引,段里面元素个数等信息,所以根据初始化函数sub_4013B4,我们可以重构一个结构体(上图是已经重构完的了)

然后就是一系列程序名,指令,数据等的输入,这里不作详细分析,直接查看run这个函数

发现这里的ida并不能反汇编得很清楚,所以只能肝汇编代码。

可以发现,其实图中左侧部分的代码块就是典型的for语句,但是问题出现在左下角的代码块

我一开始看到这里的汇编,感到很奇怪,于是就去利用gdb动态调试了一波,感觉更加奇怪了,虽然隐约感觉得出来他是一个跳转表,但是发现他是靠溢出实现得跳转(之前了解到的条状表是像switch那种具有一定规律的),然后那晚的话,教授刚好在工一,向他讨教之后,发现其实这里就是简单依靠目标地址减去数组本身地址得到的跳转表(ORZ)。然后再观察以下图中右上角以及结合gdb动态调试,右上角应该就是我们输入伪指令时的执行了。

这里给出了所有我们可以使用的伪汇编,除了load和save,其他的和我们80-86平台的无异,这里不一一分析,直接看save和load这两个漏洞指令。

这里可以看到,load指令将我们运行栈栈顶的元素取出来,当作一个索引idx,然后再将data_addr->section_ptr + 8 * (data_addr->numb + idx 这个元素给放到栈顶,由于没有对我们输入的栈顶的元素给检查,所以这里可以产生任意读功能。

再来看看save函数,这里的save主要是将运行栈的栈顶取出来,作为index,然后将后来的栈顶元素取出,作为value,然后将对应将对应的位置赋值为value:*(8 *(data_addr->idx + index) + data_addr->section_info_ptr) = value
这样的话,save便实现了任意写。

漏洞利用

这里我主要时参考了知乎上认识的四川大学的一个pwn爷的思路

Step one

攻击思路:

instruction='push push save'
data=str(0x0404088)+' '+str(-3)

查看一开始堆的分配

查看正常运行时,运行栈(靠堆实现)的情况(push push add pop || 1 1)

对比我们攻击脚本时的运行栈的情况

可以看到,运行栈中实际放置我们输入数据的地址已经被修改了

Step two

攻击思路:

instruction='push push save push load'
data=str(0x0404088)+' '+str(-3)+' '+'-12'

先看看实现的效果

可以看出,我们的0x404088已经被覆盖成我们的put@got了,这就是load实现的效果,因为我们的0x404088和我们的puts@got刚好相隔13(12+1)

Step three

既然已经修改0x404088为我们的put@got表,那么我们可以根据libc里面system函数和puts@got的偏移,再利用add这个指令去修改puts@got这个值为system函数

攻击思路:

instruction=’push push save push load push add’
offset = -(libc.sym[‘puts’] - libc.sym[‘system’])
data=str(0x0404088)+’ ‘+str(-3)+’ ‘+’-12’+str(offset)

看看实现的效果

可以看到,这个地址已经被我们覆盖为system函数了

Step four

前面已经做到把system函数写到puts@got表附近了,再看看我们ida

可以看到,这里调用了puts函数去打印我们的程序名,我们第二步的时候实现了利用save指令去修改0x404088这个地址,反过来,我们也可以用save去修改我们的puts@got表为system函数,那只要我们把程序名设置为system函数的‘/bin/shx0’,然后程序最后打印时便会触发这个函数,从而提权成功。

攻击思路:

ins=’push push save push load push add push save’
data=[data_addr,-3,-12,offset,-12]

最后本地提权成功

总结

这是我本人第一次去做的虚拟pwn相关题目,这道题是十分基础的,但是其实这么基础的题,在比赛中如果真的给我遇到了,自己真的不一定可以找得到思路去解决。回头看看思路,其实感觉就是利用了idx越界,然后去修改一些关系表的指针实现的,感觉不少题目都是这种思路,像今年国赛的那道nofree,亦或者说tcache heap的常用套路。

其实这篇博客因该在三天前就应该搞定的了,但是最近实在是有点忙,而且之前编辑一半的时候忘了保存了,昨天又是广外的羊城杯,说起羊城杯,自己真的是拉跨,给一道简单题卡了两三个小时,后面发现只是一个版本数据问题(一度质疑服务器或者docker出问题了),然后去搞了一道REpwn最后一直没有思路,逆向水平太差了,看汇编能力不够,后来一点多的时候就睡觉了,起床后发现,俺们的师兄jb大佬居然肝到凌晨五点,肝出来了一道Java web,对比一下,自己实在惭愧。

最终队里web大佬差一道web就把web全AK了,而二进制只是输出了一道(惭愧)

最后说一下最近打算更的博文的,明后两天打算学习Linux内核的进程线程基础(看源码,一些重要的数据结构,以及结合自己本身的教科书),然后去学一下iot安全入门(了解架构基础吧),应该会总结两篇博客出来。

原文地址:https://www.cnblogs.com/T1e9u/p/13642495.html