关于vm pwn的学习总结
目前就做了两道vm pwn的题目先简单总结一下,这类题目逆向量较大,如果有分析不懂的函数或者某段指令可以尝试配合gdb动态调试观察某些寄存器或内存值的变化来猜测其功能。漏洞点大多为数组越界可以写或者任意读来劫持hook或者got表等等。不一定每个指令都要具体分析明白,个人认为去关注漏洞指令,其他指令用到哪个去简单分析哪个
[OGeek2019 Final]OVM
保护策略
程序逻辑
首先程序申请了一块堆空间,程序结束的时候可以往里面输入东西,然后将其释放掉。随后询问了PC和SP寄存器的值(所谓的寄存器就是在bss段上开辟的一片数组),而pc和sp在这道题里没有任何用,接着要我们要输入指令的个数。
上面代码的注释写的很清楚了,下面的代码在while循环里的部分是处理指令的部分,而最后read函数去往堆块里输入数据,再将这个堆块free掉
在execute函数里将我们输入的每个指令都进行了分析,大概就是用c语言来实现了汇编的指令,首先每个指令都是四字节,最高字节是一个操作码(这个操作码用if来判断,这个指令是干啥的),然后另外三个字节是操作数,以add指令为例,首先用HIBYTE这个宏判断最高字节是否为0x70,如果是0x70就执行
reg[high]=reg[low]+reg[medium]
很明显这是个add指令。通过reg数组以及配合其索引来进行的操作,索引就是指令的各个字节。
下面这个指令是将具体的数值赋值给reg数组里的某个元素,只有赋值完毕,上面的add指令才有用
而本题的漏洞则在下面两个指令
reg[low]的值可以控制,这意味着memory的索引可以溢出,从而去篡改某些指针。
因为无法篡改got表,所以把利用点放到程序最后往comment的输入上(如下)
如果我们能篡改comment这个指针的话,就意味着程序的最后可以任意地址写,并且还调用了free函数,那就利用数组溢出将comment改成free_hook-8的地址,最后输入字符串/bin/sh以及system的地址过去,执行free函数的时候则获取shell。
利用思路
首先我们要把free_hook-8的地址写到一个地址上,然后将这个地址利用数组溢出写到comment上。而第一步我们需要做出来一个free_hook-8的地址,考虑到bss段上方是got表,我们利用负数索引就可以实现地址任意读取,这里我读取的是stderr的地址,将其读到了reg[4][5]的位置(因为地址是八字节,而一个数组元素是四字节,所以需要两个数组元素放一个地址)
而后用add指令将stderr的地址改成free_hook-8,最后将free_hook-8赋值到comment的位置即可。具体情况参考exp
EXP
from tools import * |
ciscn_2019_qual_virtual
保护策略
程序逻辑
控制堆块与text data stack
程序最开始分配了data段,text段和stack段。他们实现的方式都是用一个控制堆块来存放申请出来的这个段的指针,而返回的是控制堆块的地址(以stack段举例,如下)
malloc先是申请了0x10的内存出来,当做控制堆块,而后申请了8*0x40的内存当做stack,将其地址赋给s,而s这个指针存放到了ptr这个控制堆块里,最后返回ptr。
获取操作码的函数
这里的实现思路是检测我们输入的字符串中是否出现了指令字符,比如push pop add等等,然后将对应指令换成操作码来存储到text段上(赋值如下),40144E函数将ptr[i]存放的机器码给到了a1(text段)
上面需要注意的是strtok函数,strtok函数会遍历delim中的每一个字符,如果delim中有任何一个字符在第一个参数中出现,那么就会把这个字符当做分隔符进行分割,使用过strtok函数一次后,之后的每次往下分割只需要让第一个参数为NULL即可。
获取数据的函数
这个函数是获取用户输入的每个数据,将其存储到data段上,也是以delim分割(这个data段是和text段以及stack段配套使用的)
处理指令函数
这个execute函数可以对之前输入的每个指令进行处理,需要注意的是下面的puts(s),因为s可控,并且程序可以被篡改got表,所以之后有机会可以考虑将puts的got表劫持为system的地址,从而在此处获取shell
然后下面分析几个典型的函数
下面这个函数是从a1里面获取一个值,存放到a2指向的位置。
结合上图来说,(get函数)就是从第一个参数中取一个数据放置到v6中,从而识别出不同指令。
push函数
接着是push函数,从函数引用这里看出push函数需要一个stack的地址和data地址(如下)
进入内部的话是依次调用了这两个函数,前者是上面分析过的get函数,将data段里的一个数据取出来给v3,而第二个函数是将v3的值赋值给stack,具体内部实现的过程就不放了,因为我分析的不是十分透彻,我主要是通过动态调试观察函数执行前和执行后stack data text以及寄存器里的变化得出来每个函数的作用
add函数
然后是add函数,内部是用了两个get连续从stack里面取两次数据,相加后覆盖了第一个操作数将其放回。
save函数
发现就一个参数stack(注意这个stack是控制堆块的地址,而控制堆块里存放的地址才是真正指向stack的)
内部实现如下,先是从stack里取了两个数据,红框里才是最重要的部分,简单分析一下,*(stack+12)是stack中存储元素的个数,再加v2(可控)的值乘以8加上*stack(*stack就是真正stack的地址,而本来的stack是控制堆块的地址)得到最后的地址,save函数就是将v3的值写入最后这个地址里。
很明显v2是可控的,因此可以利用*stack加上一个可控偏移来实现任意数据写入
load函数
load函数与save函数相反,它的漏洞最后可以利用为从任意地址读出数据放入栈中(如下),同样是因为v2可控,这样可以利用*stack加上可控偏移将任意地址(前提是任意地址和*stack存在固定偏移)中的数据读入到栈中
利用思路
综上所述,我们考虑劫持puts的got表为system地址。先将puts的地址用load读入到stack中再用add函数加上一定的偏移得到system的地址,再利用save函数将system的地址写入到puts的got表。
调试过程如下:
下图为正常情况下stack与其控制堆块的关系,可以很明显的看到控制堆块里存放的是stack的指针,如果我们想去篡改got表,第一件事就是要将这个地址修改成got地址附件的地址。
能否直接将控制堆块中存放的地址修改为puts的got地址?
不能,如果修改后的话,*(stack+12)就会拿到一个超级大的值被当做索引(而这个超级大的值实际上是函数的真实地址),需要注意的是控制堆块中的地址决定了stack位于何处,如果更改为puts的got地址后,之后的push操作则会对各个函数的got表进行破坏,并且*(stack+12)也会因为过大导致程序崩溃
所以我们得先把stack迁移到got表附近,这里我迁移到了0x4040d0,这里正好位于了got表的下方(如下图),并且这个地方正好都是内存为0,当做一个新的stack再好不过。
此时新的stack位于got表下方,我们push进来新的索引,让其为负数,这样就能通过新的stack访问到got表中的数据,将其写到新的stack
最后执行一次save函数将system的地址写回puts的got表即可
EXP
from tools import * |