我原来写了个手搓反汇编引擎的帖子,有人说垃圾,我觉得确实写的潦潦草草,这里重新仔细的写一遍
反汇编其实很简单,没有特殊处理过的elf/pe/mach-o,找到对应的代码部分,顺着往下写就行,查表-解码-输出汇编,比如基于llvm的capstone、ghidra、zydis这些,都是查表做的。这个表是什么,就是cpu指令集的手册。
我实现了一个arm的反汇编引擎,名字叫rosemary,原理和capstone,zydis这些差不多,属于重复的轮子。
引擎基于:DDI0487L_b_a-profile_architecture_reference_manual.pdf这个手册实现的,是不是最新的我也好久没有看过了。
现在进入正题
arm汇编的码表设计类似b-tree索引,以arm64为例,4字节,32个bit的一个数字
从0-31,每一个bit或者bit组合形成一个章节,比如0xx0000xxxxxxxxxxxxxxxxxxxxxxxx,这个就是arm的保留码,任何符合这个规则的编码,都可以认为是nop。
arm的码表一级目录分为Reserved/SME/SVE/Data Processing IMM/Branch/Data processing register/SIME/Load and Store,如下图:


二级码表又可以分为data processing 1 source, Pc-rel, Add/SUB/Logic等等,那么根据手册,就可以非常轻松的理解,给定一条arm指令,如果它的二进制是:
那么它就是属于Data processing Immediate, 如果它的二进制是:
那么它就是Data-processing(1 source immediate)里面的,依次类推,直到匹配到一条确定性的指令。
当匹配到一条指令时候,就需要确定这条指令的操作数是什么了,比如mov x0, 1,那么指令是mov,操作数有两个一个是x0,另外一个是常数1。
我们拿arm64的add(Immediate)来举例:

这一条add指令有三个操作数:add Xd, Xn, #imm,其中:
寄存器Xd是0-4
寄存器Xn是5-9
立即数是10-21
立即数有shift,如果sh(第22位) == 1的情况下,那么立即数要左移12位,如果是0的情况下,就不显示
sf表示的是寄存器是32位还是64位,如果是32位,那么Xn就是Wn,他们的Wn和Xn的物理位置是一样的,只不过长短不一样
有了上面基础理解,就可以设计解码器和解码数据了,按照最简单也是最蠢的办法就是
但是cpu的指令集有成千上万条的具体指令,不能做成一个巨大的switch,那样几乎是无法维护的,所以大家都是自己定义数据格式来生成解码表。
接下来是设计描述汇编arm/x86/mips的数据结构,这里就可以根据自己的喜好,比如有人喜欢lua,有人喜欢python,根据自己的情况选一门脚本语言,来描述汇编语言。
我比较喜欢写ruby,我选ruby,以Data Processing – Immediate里的add为例:
定义
一条add指令的描述数据就是这个样子的:
patterns: 描述指令的每个bit位,有0/1/x,0/1表示必须位0或者必须位1,x表示不关心。
desc:在章节部分的字符串,调试方便。
list:标识这是一个章节,如果一个item没有list,那么表示它是一条确定的指令。
param:格式是31:0,30:0,29:0,这里表示指令的第31位,第30位,第29位必须同时为0。
name:指令的助记符。
grammar:指令的语法,调试方便。
ops_desc: 描述指令的每一个oprands,以上面为例,add Wd, Wn, #imm,
t: :r_w表示操作数是寄存器类型,w表示32位寄存器,x表示64位寄存器,还有SIMD的向量寄存器。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 5天前
被neocanable编辑
,原因: 编辑一下,后面还有后续