控制流平坦化逆向

控制流平坦化逆向

author
Created
Aug 29, 2024 08:56 AM
Last edited time
Sep 21, 2024 02:07 AM
Tags
这一段只记录从cfg转到代码的过程以及可能出现的问题。
另外关于找到图中的回边,关键点还是找到存在父子关系的节点,并且判断是否有指向其父节点的边,除了传统的染色法以外,刚刚学到了一个新方法叫DFS:parenthesis theorem,其中设置了两个概念s代表一个点的进入时间,f代表遍历中一个点的离开时间,那么关于图中两个点的关系只有三种可能,不相交,以及两种包含关系。即[s(u),f(u)]被包含于[s(v),f(v)]时,u时v的子孙节点。也就是说通过这种区间判断我们也可以找到树中的父子关系。
事实上关于使用cfg还原代码,除了不透明谓词以外,还存在其他的限制,例如不可规约图,对于使包含了goto这种语句的流图,也没办法简单的还原。

收到了某位哥的227逆向代码,看了三天,方法和之前看的内容完全不同。更接近于:
这篇文章里的缩点法。本质上就是先创建一个cfg控制流图,然后不断的对这个流图进行缩点。首先是对流图中所有的单节点和其父节点进行缩点。单节点指的是入度为1的节点。然后在处理了所有的缩点以后,就可以对逻辑结构进行分类讨论了,从当前节点与当前节点的子节点的关系进行分类。例如如果当前的double节点存在node_id===left_next_node_id这种情况就很显然的此处存在一个循环,然后再进一步对while的类型去做分类。值得注意的是,之前很烦人的dowhile结构是可以被划分为某种while(true)结构的,所以或许原来的方法也是有解法的,如果存在嵌套的话,由于这种控制流平坦化,嵌套的循环一定是每个分支都保存在不同的块中,如果最外层的if没找到对应的模式,就会把这个if的块丢到最后面,先处理下一个块,然后最终按照顺序一定会把所有的点缩完。
过程里踩了一个巨大的坑就是,这位哥的节点数据结构里对下一个节点索引的保存一半是number一般是string….我手贱改了一下==,结果直接不相等了,调了2个多小时DEBUG。另外不预先统一处理switch的好处就是快很多加上其实很方便调试,但是统一处理可能能提供一个给其他代码的接口,只需要把其他的大平坦化统一为一个switch?总的来说统一控制流可能还是需要付出蛮多时间的,光是编写代码的部分就有点麻烦,我之前是用了几种不同的方法去做统一,现在想想其实还是模拟执行找到代码块的方式最简单,其他方法都还是写起来太复杂了。
看完了那份代码以后,我尝试用之前我解到另一半的BOSS大switch控制流来试试强度。基本上是没有问题的,但是那份代码使用的还是不包含起点的做法,也就是他统计num_count这个数据结构时,是直接对控制流内所有的块进行处理的,而当初始块中包含代码和引用时,就会出错,所以需要重构一下get_num_count这个函数,让其包含一个起始值,并且把这个起始值可能遍历到的块都添加进去,从这里也可以得知当你获取了BOSS的真假函数以后对这些起点进行处理时是不会报错的,它没有添加虚假分支让你无法处理的情况。
值得说一嘴的是,这位哥的代码虽然可以完美解决227的相关问题,但是还是没有解决while中可能有多个break的问题,可能多个continue也不行,一旦分支过多,情况变得复杂就还是无法很好的处理。

目前找到一个能解决的方法,是通过一些算法,识别出控制流中的循环,例如LT/SEMI-NCA算法或者迭代算法找出支配树,然后通过支配关系来找到控制流中的自然循环,这种方式能识别出所有不包含异常结构的循环,这样当控制流缩到只剩下double节点时,可以通过预处理的信息还原break或者continue结点,进行进一步的缩点。如果包含异常结构,例如goto/异常处理等则可能还是需要用tarjan等方法去做暂时不包含在这里考虑范围内。