IDA支持大多数流行编译器生成的 switch 模式,通常您不需要担心这些模式。然而,您可能会偶尔遇到由不常见或非常新的编译器版本生成的代码,或者代码的某些特性可能会阻止IDA识别该模式,这时您可能需要帮助IDA,告知它有关 switch 的信息,以便能够呈现适当的函数图,并使反编译器能够生成良好的伪代码。
switch 模式组件
常见的 switch 模式通常由以下组件构成:
-
间接跳转
这是执行跳转到处理该 switch 案例的目标块的指令;通常涉及某个寄存器保存地址值。 -
跳转表
一个值的表,包含目标块的直接地址或允许计算这些地址的其他值(例如,从某个基址的偏移量)。它必须具有特定的固定大小(元素数量),并且值可以通过移位值进行缩放。有些 switch 可能使用两个表,第一个表包含指向第二个表的索引,第二个表则包含地址。 -
输入寄存器
包含用于确定目标块的初始值的寄存器。最常见的是,它用于索引跳转表。
switch 公式
标准 switch 假定使用以下计算来确定目标地址:
target = base +/- (table_element << shift)
如果不使用,base
和shift
可以设置为零。
示例
以下是来自ARM64固件的代码片段。
间接跳转用红色矩形突出显示。以下是相同代码的文本格式:
__text:FFFFFF8000039F88 STP X22, X21, [SP,#-0x10+var_20]!
__text:FFFFFF8000039F8C STP X20, X19, [SP,#0x20+var_10]
__text:FFFFFF8000039F90 STP X29, X30, [SP,#0x20+var_s0]
__text:FFFFFF8000039F94 ADD X29, SP, #0x20
__text:FFFFFF8000039F98 MOV X19, X0
__text:FFFFFF8000039F9C BL sub_FFFFFF80000415E4
__text:FFFFFF8000039FA0 B.HI loc_FFFFFF800003A01C
__text:FFFFFF8000039FA4 MOV W20, #0
__text:FFFFFF8000039FA8 ADR X9, byte_FFFFFF8000048593
__text:FFFFFF8000039FAC NOP
__text:FFFFFF8000039FB0 ADR X10, loc_FFFFFF8000039FC0
__text:FFFFFF8000039FB4 LDRB W11, [X9,X8]
__text:FFFFFF8000039FB8 ADD X10, X10, X11,LSL#2
__text:FFFFFF8000039FBC BR X10
我们可以看到,在间接分支中使用的寄存器(X10
)是某种计算的结果,因此它可能是一个 switch 模式。然而,由于代码是使用尺寸优化编译的(范围检查被移动到一个从多个地方使用的单独函数中),IDA无法自动匹配该模式。让我们看看是否可以识别上述标准 switch 的组件。
该公式与指令ADD X10, X10, X11,LSL#2
相匹配(用C语法表示为:X10 = X10+(X11<<2)
)。我们可以看到,表元素(X11
)在添加到基址(X10
)之前被移位了2。X11
的值来自于之前使用LDRB
(加载字节)从X9
的表中获取的W11
,并使用索引X8
。因此:
-
间接跳转:是的, BR X10
指令位于FFFFFF8000039FBC
。 -
跳转表:是的,位于 byte_FFFFFF8000048593
。此外,我们有一个基址在loc_FFFFFF8000039FC0
和移位值为2。它包含八个元素(可以通过视觉检查或从使用7作为最大允许值的范围检查推断得出)。 -
输入寄存器:是的, X8
用于索引表(我们也可以使用W8,它是X8
的32位部分,并由范围检查函数使用)。
现在我们有了所有信息,可以通过将光标放在间接分支上并调用编辑 > 其他 > 指定 switch 习语来指定模式...
值可以用C语法(0x…)或标签的方式指定,这得益于表达式评估功能。一旦确认对话框,我们可以观察到 switch 被很好地标记,并且函数图也更新以包含新可达节点。
我们还可以使用“列出来自...的交叉引用”(Ctrl-J)查看间接跳转的目标列表。
其他选项
我们的示例相当简单,但在某些情况下,您可以利用对话框中的其他选项。
-
存在单独的值表: 当使用两级表时,即
table_element = jump_table[value_table[input_register]];
而不是默认的table_element = jump_table[input_register];
-
带符号跳转表元素: 当表元素使用符号扩展指令加载时,例如在ARM上使用
LDRSB
或LDRSW
,或在x86上使用movsx
。 -
减去表元素: 如果值是从基址中减去而不是添加(公式中使用负号)。
-
表元素是指令: “跳转表”包含指令而不是数据值。这在某些架构中使用,可以使用指令指针的增量值执行相对跳转。
例如,传统的ARM跳转使用直接的
PC
操作:CMP R3, #7 ; switch ; switch 8个案例
ADDLS PC, PC, R3,LSL#2 ; switch 跳转
通常在这种情况下表“元素”是固定大小的分支,指向实际的目标。
可选值
某些值可以默认省略,但您也可以填写它们以更完整地映射到原始代码:
-
switch 的输入寄存器:如果您只需要交叉引用以获得适当的函数流图,则可以省略,但如果您希望反编译器正确解析和表示 switch ,则必须指定。 -
第一个(最低)输入值: 输入寄存器对应于跳转表的条目0的值。在上面的示例中,我们可以看到范围检查计算 W8 = W1 - 1
,因此我们可以指定最低值为1(这也会将目标地址的注释更新为1到8,而不是0到7)。 -
默认跳转地址: 当输入范围检查失败时执行的地址(在我们的示例中 - B.HI
指令的目标)。可以使列表和/或反编译更整洁,但否则并不是严格要求的。
有关支持的 switch 模式的更多详细信息,请参见switch_info_t
结构和SDK中的uiswitch
插件源代码。如果您遇到无法通过标准公式处理的 switch ,您还可以考虑编写自定义跳转表处理器。
更多文章
添加小助手微信加入交流群: OxCSorder
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA 技巧(53) IDA switch 分析
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论