5.5 难以构成跳转表的switch(3)
图5-7中的第0项为数值0,在图5-8的地址表中查询第0项,取4字节数据作为case语句块首地址—0x0040DFAA,对应代码清单5-16中的“case 1”的首地址(还记得之前的减1调整吗?见代码清单5-16中的0040DF89地址处)。在表中,标号相同的为switch的结束地址标号(有default块则是default块的地址)。然后在地址表中第6项找到switch的结束地址,图5-8中地址表的第6项对应的地址为0x0040E02B。该地址中保存的数据按照地址方式解释为:0x0040E002对应着代码清单5-16中switch的结束地址。
|
| 图5-8 非线性地址表—Debug版 |
已知case语句数及每个case语句块的地址,如何还原每个case的标号值呢?需要将两表相结合,分析出每个case语句的标号值。将索引表看做一个数组,参考反汇编代码中将索引表对齐到0下标的操作,代码清单5-16中对齐到0下标的数值为-1,因此地址表所对应的索引表的下标加1就是case语句的标号值。
例如,索引表中的第0项内容为0(索引表以0为起始下标),在表中是一个独立的数据,说明其不是switch结尾地址下标。它对应于地址表中第0项,地址0x0040DFAA这条case语句的标号值就是(0+1=1)1。地址表中的最后一项0x0040E002是表中的第6项,这个值在索引表中重复出现,可以断定其是switch的结束地址或者是default语句块的首地址。地址表第5项0x0040DFF5对应索引表中的下标值254,将其加1就是地址0x0040DFF5的case语句标号值。
在case语句块中没有任何代码的情况下,索引表中也会出现相同标号。由于case中没有任何代码,当执行到它时,则会顺序向下,直到发现下一个case语句不为空为止。这时所有没有代码的case属于一段多个case值共用的代码。索引表中这些case的对应位置处所保存的都是这段共用代码在地址表中的下标值,因此出现了索引表中标号相同的情况。
总结:
- mov reg, mem ; 取出switch变量
- sub reg,1 ; 调整对齐到索引表的下标0
- mov mem, reg
- ; 影响标记位的指令
- jxx xxxx ; 超出范围跳转到switch结尾或default
- mov reg, [mem] ; 取出switch变量
- ; eax不是必须使用的,但之后的数组查询用到的寄存器一定是此处使用到的寄存器
- xor eax,eax
- mov al,byte ptr (xxxx)[reg] ; 查询索引表,得到地址表的下标
- jmp dword ptr [eax*4+xxxx] ; 查询地址表,得到对应的case块的首地址
如果遇到以上代码块,可判定其是添加了索引表的switch结构。这里有两次查找地址表的过程,先分析第一次查表代码,byte ptr指明了表中的元素类型为byte;然后分析是否使用在第一次查表中获取的单字节数据作为下标,从而决定是否使用相对比例因子的寻址方式进行第二次查表;最后检查基址是否指向了地址表。有了这些特征后,即可参考索引表中保存的下标值来恢复索引表形式的switch结构中的每一句case原型。