接上文
我回答一下一些邮件上的问题:
第一rosemary开源的问题,rosemary原来设计的是专门来做android静态分析的,后来我发现,其实可以做更多的时候,已经来不及整理了,导致binary loader的部分和分析引擎的部分耦合太深,我正在支持iOS,iOS支撑完了,这里的耦合就可以完全解开了,把binary loader和分析引擎分开,就可以支撑起多种文件结构,如:elf/pe/macho等等。
第二rosemary引擎没有用capstone/zydis/llvm table gen这些东西,后端是一个ruby写的dsl引擎来支撑起来的,dsl描述的是汇编语言。算是自己造的轮子,能支持到arm到sve/sme/sve2/sme2,我看了arm到文档,我参考的文档还是最新的。对于X86/MIPS这些,后端的DSL引擎的设计可以支持到,但是个人能力有限了,有时间再支持了。
第三rosemary没有想做成一个IDE,因为IDA/JEB/BN/GHIDRA/R2这些太成熟了,没有必要舍近求远,我希望它能做成一个完整的静态分析引擎的pipeline,能提供出一套c/c++/python的API即可。
上一篇文章讲了静态分析最重要的一个部分 - 控制流恢复,所有控制流恢复的分析软件都存在一个重大的挑战就是间接跳转恢复,在arm里面就是br Xn,跳转的参数是一个寄存器,而这个寄存器中的值是不确定的。包括IDA/Binary Ninja/Ghidra/R2都是这样,没有办法完完全全的将所有的间接跳转恢复出来。
那么这个br Xn到底是怎么来的呢?它有几种情况
switch case不会都编译成br Xn,如果case的情况少,比如:
这样的switch case就会被编译成branch,因为case的路径少,完全可以用if/else,在汇编语言里就是ifeq这种情况。
如果switch非常非常大,那么它会被编译成什么样呢?可以在IDA里面找一个例子:
在这里ADRL就是adrp + page_offset的一个缩写,这里的jpt_569A0这个地址存储的就是所有的case的偏移地址,如下:
那么到这里就一目了然了,编译器在这里的工作简单的说就是第一,找到所有case,第二把这些case的偏移地址存储起来,通过比较W8,加载对应的地址实现case的跳转。
vtable就是c++的多态的产物,是一堆函数指针列表;它在C++中的结构是rtti(RuntimeTypeInfo),它由一个名字是vptr的指针存储
如果要识别vtable,首先要做的就是识别rtti,能够正确的识别出rtti的typeinfo结构后,才能完整的推断出vtable的结构。
typeinfo有:
函数指针数组,顾名思义,就是一个数组,里面都是函数指针;
但是这个结构其实也是最扯淡的结构,因为这种结构都是非标准化的
我可以可以写 func_arrs = [0x1234, 0x5679] 这里 func_arrs既可能是uint类型的数据,也可能是函数地址,但是,它在编译器眼里,都是数据;
那么这种结构的识别也就是仁者见仁,智者见智了。
文章的标题是IR设计,为什么扯到了一堆跟间接跳转相关的话题呢?因为识别这些结构有的可以根据模式匹配(pattern match)来匹配,像IDA/BN/GHIDRA/R2这些老牌的产品,风风雨雨经历过编译器的多个变迁,那么它们的识别库里面有N多这样识别这些跳转的pattern,如果一个新的产品,来处理这些间接跳转,最好的办法就是通过IR的模式来处理,而不是模式匹配。
我没有了解过gcc/llvm的ir,我只写rosemary引擎的ir设计和开发
aarch64的汇编的操作数有几种: 寄存器(register)、立即数(imm)、内存(memory)、列表(vlist)、rbitlist
在没有NEON/SVE/SME的情况下,只有前三种
vlist是我自己给的定义,用来描述**{v1, v2, v3, v4}**, {v2-v3} 这种结构;
rbitlist是a32/t32结构中的bit寄存器数组
那么原则上IR的基础数据结构就只有这4种,register/imm/mem/vlist/rbitlist
我用的c语言写的,那么IR的c语言定义应该是这样的:
有了这个结构,我们可以用它来描述一条指令:adds x1, x2, #1
这样,一条adds指令的所有数组都描述完成了
可以看到,这种结构的逻辑上是嵌套的,存储结构上是平铺的。
如果仅仅有上述4种类型的IR,那么会在描述过程中越来越复杂,当规约某一种类型的结构时候会越来越麻烦,比如上面的adds,
如果adds是adds x1, #0x1, 0x2这样的情况,那么在简化IR的过程中,就会写成:
所以IR还需要有下面这些类型
有了这些,就可以简化指令了,adds就会从invoke进化成binary,从binary进化成一个assignment_reg
这样简化起来就简单了,在常量折叠的时候,完全可以根据x1的def/use来将它简化成常数3
到了这里,就能解释文章开头时候到问题了,用IR来识别jumptable/vtable等等结构。我们以一个最简单jumptable的例子来看,在IDA里,jumptable的样子是这样的:
它的真实机器码:
这段代码的意思很简单,这是一个标准的jumptable结构
在我们定义的IR里,jumptable的IR表达式如下:
有了这样的IR描述,一个jumptable就完全可以识别到了,即:
这里仅仅举了一个ir的作用,它的作用远远不止这些,这种形式的ir还能继续lift,当lift到一个极简的结构后,就可以继续做
控制结构识别(if/else/else if/switch/for/while/do while)
类型识别(uint/int/long)
local varibale恢复
栈恢复
栈平衡
函数参数溢出到栈
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 6天前
被neocanable编辑
,原因: 排版