逆向-三目运算
Posted 嘻嘻兮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆向-三目运算相关的知识,希望对你有一定的参考价值。
首先,先简单回顾一下三目运算符(条件表达式)的格式
表达式一 ? 表达式二 : 表达式三
当表达式一的结果为真时,选择执行表达式二,否则执行表达式三。
看完这个格式,很明显这是一个有分支的结构,那么编译器会老老实实的都按分支语句进行编译么,下面我们还是需要来分情况讨论一下。
1.当表达式二或表达式三不为常量
2.表达式二或表达式三为常量
2.1 当表达式一为0的等值比较,表达式二和表达式三差值为一
2.2 当表达式一扩展为非0等值比较,表达式2和3扩展为其他任意常量时
2.3 当表达式一扩展为区间比较,表达式2和3为其他任意常量时
OK,我们下面主要来看一下下面的这几种情况,首先,来看情况一,这种情况是无优化的情况,对应的汇编代码就是一个分支结构
int main(int argc, char* argv[])
printf("%d",argc == 0 ? (int)argv : -1);
return 0;
对应的汇编代码
.text:00401000 mov eax, [esp+argc]
.text:00401004 test eax, eax
.text:00401006 jnz short loc_40101D
.text:00401008 mov eax, [esp+argv] ;argc为0的情况,直接拿argv
.text:0040100C push eax
.text:0040100D push offset unk_407030
.text:00401012 call sub_401040
.text:00401017 add esp, 8
.text:0040101A xor eax, eax
.text:0040101C retn
.text:0040101D ; ---------------------------------------------------------------------------
.text:0040101D
.text:0040101D loc_40101D: ; CODE XREF: _main+6↑j
.text:0040101D or eax, 0FFFFFFFFh ;当argc不为0时,eax = -1
.text:00401020 push eax
.text:00401021 push offset unk_407030
.text:00401026 call sub_401040
.text:0040102B add esp, 8
.text:0040102E xor eax, eax
.text:00401030 retn
这种情况没啥好记录的,所以主要看下面为常量的优化,对于常量的优化,debug和release下都是相同的。
首先来看2.1,这种情况一定要好好理解,因为这种情况其实可以说是下面情况的原型,后面两种都是在此基础上进行扩展的而已。先来看例子
int main(int argc, char* argv[])
printf("%d",argc == 0 ? 0 : -1); //例一
printf("%d",argc == 0 ? 1 : 0); //例二
return 0;
这里的例子有2个,先来看例一的反汇编
.text:00401001 mov esi, [esp+4+argc]
.text:00401005 mov eax, esi
.text:00401007 neg eax ;neg指令是对其求补(0-eax)
.text:00401009 sbb eax, eax ;sbb r1,r2 相当于 r1 = r1 - r2 - CF
.text:0040100B push eax
对于指令的说明写在上面的注释中了,对于sbb指令,可以发现这里是自己减去自己,那么肯定结果为一,所以决定sbb的值肯定在于CF位是多少,而CF位又取决于上一行的取补指令,下面我们来分支讨论一下CF位的情况
.text:00401007 neg eax ;if eax == 0 CF = 0 else CF = 1
.text:00401009 sbb eax, eax ;if CF == 0 eax = 0 else eax = -1
当eax为0时,其求补结果为0,所以CF位不会进位(CF=0),当CF不会进位时,其结果自然为零了。反之当eax不为零时,对其求补必然会产生进位,因为这里求补的本质是用零去减该数,那么很明显只有零值才不会产生进/借位。
下面来看例二
.text:00401016 xor ecx, ecx ;需要注意这里要清0
.text:00401018 test esi, esi
.text:0040101A setz cl ;当ZF=1时,cl=1 else cl = 0
.text:0040101D push ecx
setxx r8
当条件(标志寄存器)满足,r8寄存器会被设值1
对于setxx类型的指令上面解释了,这里汇编就比较明显了,当esi的值为0时,cl为1,那么反之cl等于0。
下面再来看2.2的情况,这种情况其实相当于对于上面情况的扩展
int main(int argc, char* argv[])
printf("%d",argc == 77 ? 88 : 66);
return 0;
对应的汇编代码
.text:00401000 mov eax, [esp+argc]
.text:00401004 sub eax, 4Dh //4dh=77
.text:00401007 neg eax
.text:00401009 sbb eax, eax ;这里构造结果 0 和 0xFFFFFFFF(-1)
.text:0040100B and al, 0EAh //0EAh=-22
.text:0040100D add eax, 58h //58h=88
.text:00401010 push eax
首先对于401004处的汇编代码,减去一个77,相当于平移对齐到零值,这样子就转化为上面2.1的例一情况了,也就是减去77后,其值为零,那么必然结果为真,反之其值必然不等于77(为假)。
下面对于40100B和40100D处的代码进行分析,这里还是需要分支来讨论,毕竟上面sbb的结果就是一个分支情况
.text:00401009 sbb eax, eax ;eax = 0 || 0xFFFFFFFF
.text:0040100B and al, 0EAh ;if eax == 0 eax = 0 else eax = 0xFFFFFFEA
.text:0040100D add eax, 58h ; eax += 58h
if eax == 0
eax = 0 + 58h = 58h = 88
else
eax = 0xFFFFFFEA + 58h = 42h = 66
通过最后的分析也能得出当eax等于零(相当于等于77),其值为88,否则其值为66。
最后再来分析一下最后这种情况,这种情况其实是对上面案例的综合体现,其实通过上面的分析,可以发现编译器其实优化的特点就是构造出一个零值和一个0xFFFFFFFF值(-1),然后对其做and和add的操作。
int main(int argc, char* argv[])
printf("%d",argc >= 66 ? 77 : 55);
return 0;
对应的反汇编代码
.text:00401004 xor eax, eax ;注意这里eax需要清0
.text:00401006 cmp edx, 42h ;42h=66
.text:00401009 setl al ;当edx小于66时 al=1 else al=0
.text:0040100C dec eax
.text:0040100D and eax, 16h ;16h=22
.text:00401010 add eax, 37h ;37h=55
.text:00401013 push eax
下面我们先来找出上面所说的编译器优化的特点-构造0和-1
.text:00401009 setl al ;if edx < 66 al=1 else al=0
.text:0040100C dec eax ;if edx < 66 eax = 0 else eax = 0xFFFFFFFF
通过了上面两行的代码执行后,这里子就构造出了0和-1值,对于剩下的情况其实就和2.2一样了,这里简单过一下
.text:0040100C dec eax 此时 eax = 0 || -1
.text:0040100D and eax, 16h ;16h=22
.text:00401010 add eax, 37h ;37h=55
if eax == 0 ;也就是小于66的情况
eax = 0 + 37h = 37h = 55
else ;大于等于66的情况
eax = 16h + 37h = 4dh = 77
看完上面的分析,其实按照我们的分析还原一下
argc < 66 ? 55 : 77
可以发现,编译器生成的条件表达和源代码是相反的,不过这样也不影响代码的还原,比较只是顺序交换了一下而已。
对于这种情况,高版本还使用了CMOVXX系列的汇编指令进行优化
cmovxx S,R
当条件(标志寄存器)满足,S=R
如对上述的代码,使用vs2015编译出的汇编如下:
.text:00401043 cmp [ebp+argc], 42h //66
.text:00401047 mov ecx, 4Dh //77
.text:0040104C mov eax, 37h //55
.text:00401051 cmovge eax, ecx //大于等于条件满足执行
.text:00401054 push eax
argc >= 66
eax = ecx = 77
else
eax = 55
所以对于高版本编译器来说,此时还原高级代码可能会更加的轻松。
以上是关于逆向-三目运算的主要内容,如果未能解决你的问题,请参考以下文章