首页 > 二进制分析 > switch 语句的反汇编浅析
2018
09-12

switch 语句的反汇编浅析

switch 的简单情景(case 不超过 3 项)

    首先,我们分析一下 switch 语句的一种简单情景,我们可以用 C 写出如下如下代码。

switch 语句的反汇编浅析 - 第1张  | 阿龙咖啡

    编译后用 OllyDBG 载入,它将显示出如下的反汇编代码。

switch 语句的反汇编浅析 - 第2张  | 阿龙咖啡

    首先,我们可以看到 ESP 减少了 8,除了定义变量 a 外,编译器还分配了一个临时变量(这里暂且叫它 t)用于比较。t 被赋值成 a 的值,然后与立即数 0x10,0x20,0x30 依次比较。如果有一项相等,那么就跳转到 case 里面,如果都不相等,就会无条件跳转到 default 里面。执行完 case 或 default 里面的代码之后,就会无条件跳转到 end 的位置。

 

switch 跳转表情景(case 超过 3 项)

    上面是 switch 中比较简单的情景,但是当 case 项超过 3 项时,情景将发生很大的变化。我们可以先编写如下 C 语言代码,这里 switch 的 case 项已经超过 3 项。

switch 语句的反汇编浅析 - 第3张  | 阿龙咖啡

    编译后,用 OllyDBG 载入,可以得到如下的反汇编代码。与之前的简单情景类似,除了定义变量 a 外,编译器还分配了一个临时变量(这里暂且叫它 t)用于比较,t 被赋值成,接着我们可以观察到 t 减了 0x10,然后与 0x8A 做比较。实际上,我们不难看出 t 先减去了 case 项中的最小值,然后与 case 项中的最大值和最小值的差进行比较。这样做的目的是首先排除极端情况(如果 t 比最大值要大,或者比最小值小,那么肯定是 default),接下来的 JA 指令就能排除这类极端情况,提高程序执行效率。

    说到这里,有些人可能不能理解为啥要先减去最小值,然后再与最大值和最小值的差进行比较。其实道理很简单,假设变量的值是 t,最小值是 min,最大值是 max,我们很容易建立一个比较关系,t – min 与 max – min 之间的比较。这里很容易化简成 t 与 max 的比较,如果 t 比 max 大,说明这是极端情况应该去 default。还有一种可能就是,t – min 的时候,t 比 min 小,这样寄存器就会溢出,t 会变得很大很大且大过 max,这也会变成极端情况。换言之,这样的一套操作下来,程序能保证 t 在 min 和 max 之间是能被 case 接受的,否则就是极端情况而进入 default。

switch 语句的反汇编浅析 - 第4张  | 阿龙咖啡

    我们往后看,假设变量的值不是极端情况,那么程序就会把 t 的值赋值给 EDX 寄存器,注意,这里的 t 已经减去了 case 项的最小值,换言之,如果把 case 项中的最小值看做起始点 0,最大值看做最大值与最小值的差,那么 t 就是介于最小值与最大值之间的某个点。

    接下来,eax 被赋值成某个基址 + EDX 的值。我们跟进这个基址可以看到以下的内容:

switch 语句的反汇编浅析 - 第5张  | 阿龙咖啡

我们仔细分析一下 EDX 的值(也是 t 的值)与基址起始的字节数据的内在联系。

  • 当 EDX == 0x0 时,取出数据 0x0 给 EAX。
  • 当 EDX == 0x22 时,取出数据 0x1 给 EAX。
  • 当 EDX == 0x34 时,取出数据 0x2 给 EAX。
  • 当 EDX == 0x8A 时,取出数据 0x3 给 EAX。
  • 当 EDX >= 0 且 EDX <= 0x8A 时,除去以上情况,将取出数据 0x4 给 EAX。

仔细观察可以发现,这里的内存数据从基址偏移 0x0 ~ 0x8A 的每个字节都是一个指示值,在此例中 EAX 可以被赋值成五个值 0,1,2,3,4,5。EDX 实际上也就是 a 减去 case 项最小值的结果,所以,最终我们能建立一个映射关系:

  • case 0x10 –> EAX = 0x0
  • case 0x32 –> EAX = 0x1
  • case 0x44 –> EAX = 0x2
  • case 0x9A –> EAX = 0x3
  • default –> EAX = 0x4

而 EAX 的值最终会被用于跳转表,跳转表里保存了每一个指示值所对应的内存地址,这些内存地址就是每个分支的入口。回到反汇编,接着程序将执行一个跳转,目的地址是某个基址 + EAX * 4,让我们在数据窗口跟随到这个基址:

switch 语句的反汇编浅析 - 第6张  | 阿龙咖啡

我们很容易发现,这个基址就在之前指示器基址的上方。本例中它存储了 0x14 字节(20 字节)的数据,每个地址是 4 字节,那么就是五个地址,十六进制分别是 004095E6,004095E5,00409604,00409613,00409622。这刚好是程序中各个分支的入口地址。EAX(本例中 0x0 ~ 0x4)的值恰好是指示取这五个值中的哪一个值。

switch 语句的反汇编浅析 - 第7张  | 阿龙咖啡

在本例中,EAX 会取到 0x4 这个值,最终会 JMP 到 00409622 这个地址,也就是 default 分支的入口点,这样 switch 就执行完毕了。


打赏 赞(1)
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏

最后编辑:
作者:Yenyu
Yenyu
编程爱好者

留下一个回复

你的email不会被公开。