模拟栈帧修复
模拟栈帧修复
填坑,模拟堆栈的复原。
有关栈帧汇编复习
1 |
|
push和pop可由mov和sub等两步操作组成,所以开辟堆栈时一般是sub esp,xxx 之后通过mov来使用栈空间,而不是push。这样能提高指令的执行效率。
常见栈帧的模板,以一个debug版本的加法为例。
练习的话最好自己跟一个函数用excel画一下堆栈图。
1 |
|
其实栈帧本身有系统默认的esp和ebp来维护,如果我们将其换用其他的寄存器,在内存的基础上模拟堆栈,那么IDA反编译的效果会大打折扣,从而起到保护作用,NaCl一题就是运用了该类技术。
例题
事先可以用Lumina和Finger恢复一下库函数符号,减少不必要的调试操作。
1 |
|
跳过来endbr64
检测是否劫持,与原本函数开头的push ebp
和 sub esp,xxx
大不相同,这里是对r15进行操作,为了方便观察右上开了一个子窗口专用来跟踪R15。
1 |
|
之后又发现类似结构
1 |
|
该处的亮点在于使用r15模拟了一个call loc_8080720的操作,call之后执行8080980处代码,中间数据为垃圾指令。
观察右侧窗口即为调用函数时入栈的retadr
和ebp
,并且对于函数调用的代码块用到了endbr64来标识,之后则是开辟新的栈帧。
程序是64位的,所以rbp和返回地址以及对栈的操作都是以8字节为单位。其实这个rbx的内容也不是栈底,应该是保持结构。
逻辑段中也插入了一些垃圾数据。
函数调用的结构已经知晓,接下来看他调用完是如何恢复堆栈的。
1 |
|
所以整体堆栈由r15来维护,并且模拟了call和ret,同时逻辑段中还有垃圾指令。
我们预期是恢复堆栈由esp和ebp来控制,恢复call和ret函数并且去掉逻辑段中的垃圾指令,这项工作将通过使用ipy来解决。
1 |
|
本来是要这把r15修成rsp的,但是机器指令的长度不一致,改动的话会比较大,个人看到了PZ师傅的分享,只需要把控制流的call和ret修的清楚一些就能生成可读的反编译代码了。
1 |
|
根据上述硬编码的格式给出Patch
脚本,感谢P师傅的思路分享。
1 |
|
运行后,将Patch应用到一个附件上,IDA重新进行解析,反编译后结合调试信息恢复结构体。
对比上一篇文章调试汇编得到的加密,结构基本一致。
1 |
|
来喝杯茶吧🍵🍵🍵
1 |
|
修复结构体后函数如下。
解密就不在赘述了,在上篇文章中有写。
由于本题的逻辑代码块较少,并且Tea加密的汇编比较有特点,所以通过调试读汇编的手段也能快速解题,但一旦代码量大起来就G了。
End
其实上述操作并没用完全解决栈指针换寄存器的问题,我们只是凭借对call和ret的修复使程序控制逻辑恢复,达到反编译的效果。这样做的缺点是我们不得不把 r15模拟的栈空间当成结构体来处理,其实修复起来也是有一定难度的,如果函数调用比较深,那么还是没能简化分析。
彻底修复是把r15换成rsp,不过通过IDApython的话感觉会比较繁,使用r15和rsp的指令长度可能会差1,改动量会比较大,目前的尝试以失败告终。😭😭😭
Intel-x64汇编指令机器码对应列表 – 更具体的硬编码可以通过在x64dbg或OD中写汇编来观察。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!