0x00 前言
经过将近2个月的时间,看完了b站上南大的静态程序分析课程,并且完成了其oj上的作业,在这里记录一下在做题过程中,遇到的一些坑点,文章不会贴源码,只记录一下思路,因此大家可以放心阅读。
PS:注册oj的邮箱是向读研的同学借的。
0x01 活跃变量分析和迭代求解器
第一题可以算作一道让学生来熟悉tai-e平台的签到题,因为具体实现算法在课程中已经讲到了,所以只需要按照作业中的提示,完成每个函数就行了。但是对于很久没做过java开发的同学来说,在实现transferNode方法的时候,可能会犯下面的低级错误:
计算OUT[B] - def[B]的值时,直接调用了out.remove(def)方法,这时虽然IN的值算出来了,但是OUT的值却被修改了,因此在最开始,需要使用out.copy()方法拷贝一份out的值,然后调用拷贝对象的remove(def)方法就可以了。
0x02 常量传播和 Worklist 求解器
这一题是分析整数类型的常量传播,题目对要程序要分析的范围划分也很明确,因此在实现transferNode方法时,仅分析DefinitionStmt语句就可以了。这道题应该是我花费时间最多的一道题,一度因为一个测试用例而WA,直到考虑了下面这个条件才AC:
对于除以 0 的情况(出现在 / 和 % 中),无论被除数是NAC,还是其他什么值,他的结果都应该是 UNDEF。
0x03 死代码检测
做这道题时,比较难处理的是SwitchStmt,因此我们需要先去了解switch语法在IR和cfg中的表现形式,相信有了前两道题的经验,大家应该也知道了IR文件都在sootOutput文件夹下,cfg会被输出到output文件夹下,后缀是.dot,需要借助Graphviz工具,使用 dot -Tpng xxx.dot -O 命令即可将.dot文件转换为png来查看,但前提是需要把pascal.taie.analysis.Tests#DUMP_CFG这个属性设置为true。相信大家借助cfg图,就会有处理SwitchStmt的思路了。
0x04 类层次结构分析与过程间常量传播
这道题实现起来比较简单的是实现类层次结构分析,比较难的是实现过程间常量传播。但最后发现,过程间常量传播只是需要花点精力来处理那4种类型的边,只要把算法设计出来,测试用例可以一把过,反而oj上的类层次结构分析的测试用例,却一直没通过,后来通过查看tai-e提供的api,最终是AC了,情况是这样的:
1、开始我没注意到DefaultCallGraph类有addReachableMethod方法,但是我注意到了他有reachableMethods这个属性,因此我就用这个属性保存了我找到的reachableMethod,然后通过遍历这个方法里所有的stmt,来获取所有的callSite,最终实现了整个算法,我手动实现的这个算法的结果肯定是没问题的,要不常量传播的测试用例不可能通过。但是在oj上,我所有的类层次结构分析的测试用例都没通过。
2、后来我改用了addReachableMethod方法来保存reachableMethod,跟进这个方法我发现他的实现跟我一样,而且他顺带也保存了方法里所有的callSite,所有直接用callGraph.callSitesIn(m)方法就可以获取m方法中所有的callSite,用这个方法最终通过了oj上所有测试用例。
0x05 指针分析
无论是非上下文敏感的指针分析,还是上下文敏感的指针分析,课程中讲的都非常清晰,因此只要把课程完整跟下来,充分理解了具体的算法,基本上都可以一次性AC,这里也没啥坑点,就不多说了。
0x06 Alias-Aware 的过程间常量传播
同上,这道题只要把作业中的描述都理解了,理解了什么是别名,然后按照作业的指引,正确处理LoadField、LoadArray、StoreField、StoreArray这4种stmt就可以了。
不过可以在initialize方法中提前保存一下每个变量的别名,以及所有静态的StoreField,和所有静态的LoadField,方便后面使用。当然这也不是必须的,也可以在后面需要这些信息的时候再实时计算,不过这种方式效率会很低。
0x07 污点分析
最后一个作业,花费了很长时间在算法设计上。
1、Sources规则比较好处理,在指针分析里合适的地方插进去就行了。
2、TaintTransfers规则同样也是需要在指针分析里合适的地方进行处理。但是这里处理的结果该怎么保存呢?这里想了很久,最终想到需要用一个图来建立污点传播的关系。这个图的数据结构可以参考PointerFlowGraph。基于污点流图,就可以在指针流图上进行污点传播了。需要注意的是,在传播污点数据时,只传播污点对象就行了,不要把普通的指针分析的对象也给传播了。
3、根据作业中给的提示,可以在collectTaintFlows()方法中实现处理 Sinks的规则。这时遍历程序所有callSite即可。当然也可以提供一个方法,用来保存所有符合Sink规则的callSite,然后插在指针分析的合适的位置,最后遍历这些callSite就行了,这样可能更高效些。
0x08 总结
这个作业几乎占用了我这两个月所有的周末,以及五一假期,不过功夫不负有心人,最终还是通关了。终究是有收获的,对静态程序分析的原理有了更深入的了解,总之继续加油吧,网安的路上,需要学习的还有很多!