飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 3176|回复: 1

[C/C++] 运算符巧妙原理解析

[复制链接]
  • TA的每日心情
    慵懒
    2019-3-12 17:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2010-8-24 20:11:17 | 显示全部楼层 |阅读模式

    最近一直研究一个对个人而言很有价值的一个LIB库的逆向。在今天下班后突然灵感闪现,这个断断续续逆了接近一周的核心管理类。终于在今天给逆完了。在最后一个函数里,碰到了之前基本没有用过的一条指令。(呵呵,高手见笑了!)当然光看单句的汇编指令,是没有办法看出具体的作用的,而且还很可能会认为原作者本来就是用汇编来实现的!呵呵,先不废话,先贴出反汇编代码一睹为快:

      mov        dword ptr ,64h   // int b
      xor         eax,eax
      cmp        dword ptr ,0
      setg        al   
      sub         eax,1
      and         eax,64h
      add         eax,0C8h
      mov         dword ptr [a],eax   // int a

         今天的LIB里面的那段迷惑的代码就跟这段代码一致,唯独a、b变量不一样。当然这个不影响结果。一开始可能会对setg这条指令的用途不了解。二是看下面蓝色的三条指令,什么又是减,又是and,又是add一些莫名奇妙的立即数。还真让人迷惑这段代码翻译成C++将怎么写。难道就一句一句的翻译?这样的话恐怕一条汇编就是一句C++。而且到了setg这条指令时还真不知道怎么单独的将其翻译成C++的什么语句。呵呵!这可能也就是逆向所带来的乐趣之一吧(个人观点)!

         好了。不废话,先分析下。首先b是一个变量,首先被赋值成0x64(100)。然后将b与0进行比较,如果有心的朋友会觉得奇怪,这个cmp的下面一条语句怎么不是跳转语句,一般都是比较后,然后根据比较结果进行跳转。否则cmp有什么意义呢?到这里的话误导我们的就是setg这条指令了。要了解它,首先得知道cmp会影响到标志寄存器的标志位。cmp是执行的减法操作,将前面的操作数减去后面的操作数。与sub的区别就是它不将减后的值放到目的操作数中。所以cmp有可能减溢出等,从而影响到了标志位。由此一来我们就算猜测都能知道setg应该与标志位有关系。然后通过资料或者奔腾X86指令集查找表(我使用的平台是INTEL X86 CPU)。就可以知道setg指令乃是大于零为真,setg al 就是如果cmp比较后大于零,al里的值将是0x01。setg的判断表达式就是(ZF=0 and SF=OF),还有个setle( ZF = 1 or SF <> OF ),等等还有几个,这里就不一一说明。有兴趣的朋友可以去查阅。

         之后蓝色的三条指令,仔细分析会发现,如果eax为0的话,sub eax, 1后eax将是0xffffffff。之后再and eax, 64h结果eax为64h,之后再是加C8h。之后就给a变量了。然后再分析另外一种情况,假如eax为1, sub eax, 1后将是0,再and等于没运算,之后才是加上C8h,后面就一样了。前面的xor eax, eax就不说了,就是把eax清零。这样一分析,可以有个大概的C++语句的锥形。那就是三目运算符:(?:)。

        好了,这句C++语句很短,那就是:

    int b = 100;

    int a = ( b <= 0 ) ? 300 : 200;

         哈哈,很简单吧!其实逆向就是这样,分析一大段很可能翻译过来的C++代码就一句而已。这篇文章说是巧妙原理解析。这里的巧妙之处就在于编译器很聪明(MS很厉害),这些细节的技巧可以给我们很多的启发。这个三目运算在汇编层面上,首先编译器会把后面冒号两边的数字求差,差将用于and运算。b <= 100后eax要么为1要么为0。因此后面的sub eax, 1后eax要么为0xffffffff,要么为0,为0xffffffff表示b小于等于0。b小于等于0之后的and eax, 64h也就将300 - 200的差100赋值给了eax,之后再add C8h(200),便得到了300。反之,sub eax, 1后。 eax将为0。and运算后等于就没有算上差值,之后加上冒号后面的数。就是小的200了。呵呵!这些细节,MS的程序很细心啊!

         还有就是一点,这里记录的差值是有符号的,而且是固定的冒号前面的数减去后面的数。如果前面的数小于后面的数,那么这里记录的将是一个负值,原理一样。

         再举一个例子吧:

          mov         dword ptr ,1
          xor         eax,eax
          cmp         dword ptr ,0
          setg        al   
          lea         eax,[eax+eax-1]
          mov         dword ptr [a],eax

         这个例子就不用说了,直接贴C++代码:

         int b = 1;
         int a = ( b > 0 )? 1 : -1;

         或者是BOOL类型的。注意看红色指令的巧用,它直接代替了and和add指令。大家慢慢体会!   

         如果后面冒号两边任意一边是变量的话,就不会被编译成这样子了,就会被编译成普通的跳转类似于if语句了。

         好了,这里就是一点小小的心得,当是长个记性。

    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    2024-1-15 22:57
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2010-8-24 21:49:07 | 显示全部楼层
    老大在另一个境界里,仰望一下
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表