• 【黑客免杀攻防】读书笔记8


    0x1 if-else分支

    if-else分支4种状态

    1.1 以常量为判断条件的简单if-else分支

    C源代码:

    单层if-else判断,常量为判断条件

    int _tmain(int argc, _TCHAR* argv[])
    {
    	int nTest = 1;
    
    	if (nTest>0)
    		printf("Hello world!
    ");
    	else
    		printf("Hello everybody!
    ");
    
    	return 0;
    }
    

    if-else分支用的都是反比,Debug版本的if-else分支特征如下:

    010170DE mov ?????,?????    ;赋值到寄存器,然后地址与真假值对比
    010170E5 cmp ?????,0x0      ;比较数值
    010170E9 jle X9-7.010170FA  ;比较后的跳转,如果不执行分支1就执行分支2的位置
    ...........                 ;进行一定操作的分支1        
    010170F8 jmp X9-7.01017107  ;分支1执行完毕后,无条件跳转到结束位置,结束判断
    010170FA push 9-7.010A5E64  ;进行一定操作的分支2                  
    010170FF call 9-7.01013CE7  ;进行一定操作的分支2
    01017104 add esp,0x4        
    01017107 xor eax,eax        
                                
    

    先把值存放到寄存器中,cmp对比数值,跳转的方式不一定是JLE,也可能是JXX。如果执行完分支1(if)内容后,无条件跳转到结束的位置。如果没有执行分支1,那会跳转到分支2的位置,执行完后顺序执行到结束的位置。

    1.2 以变量为判断条件的简单if-else分支

    C源代码:

    单层if-else判断,变量为判断条件

    int _tmain(int argc, _TCHAR* argv[])
    {
    	if (argc > 0)
    		printf("Hello world!
    ");
    	else
    		printf("Hello everybody!
    ");
    
    	return 0;
    }
    
    

    反汇编代码:

    cmp ?????,????? ;比较数值
    jxx AAAAAA      ;比较方式
    .....some code  ;分支一
    retn
    AAAAAA          ; JXX后面的地址
    .....some code  ; 分支二
    retn
    

    release版为每一个分支后面都添加上了结束代码。

    编译器这样做其实仅增加了一个字节的体积,但是减少了一个跳转指令。

    1.3 以常量为判断条件的复杂if-else分支

    C源代码:

    两个连续的if-else判断,常量为判断条件

    int _tmain(int argc, _TCHAR* argv[])
    {
    	int nTest = 1;
    
    	if (nTest>0)  // 第一个if-else
    		printf("Hello!
    ");
    	else 
    		printf("Hello everybody!
    ");
    
    	if (nTest>0)  // 第二个if-else
    		printf("World!
    "); 
    	else
    		printf("Hello everybody!
    ");
    
    	printf("End!
    ");
    
    	return 0;
    }
    

    Debug版本汇编:

    00B170DE mov XXXXX,XXXXX   ;将数值移动到堆栈中
    00B170E5 cmp XXXXX,XXXXX   ;比较数值
    00B170E9 jle X9-9.00B170FA ;跳转
             ....A分支1行为
    00B170F8 jmp X9-9.00B17107 ;如果分支1行为执行了就跳过A分支2的行为
    00B170FA ....A分支2行为   
    00B17107 cmp XXXXX,XXXXX
    00B1710B jle X9-9.00B1711C
             ....B分支1行为
    00B1711A jmp X9-9.00B17129 ;无条件跳转,越过B分支2行为
    00B1711C ....B分支2行为
    00B17129 push 9-9.00BA5E80 ;ASCII "End!   
    00B1712E call 9-9.00B13CE7 ;printf函数
    00B17133 add esp,0x4
    00B17136 xor eax,eax
    
    

    release版生成的代码:

    不可达的分支都有编辑器初期给剪掉了,VS2015跟书上的例子不一样。进去Main()函数后直接就是输出常量的值,然后调用printf函数了。。

    00A71000  push 9-9.00A86448  ; format = "Hello! 字符串
    00A71005  call 9-9.printf    ; printf函数
    00A7100A  push 9-9.00A86454  ; format = "World! 字符串
    00A7100F  call 9-9.printf    ; printf
    00A71014  push 9-9.00A86460  ; format = "End! 字符串
    00A71019  call 9-9.printf    ; printf
    00A7101E  add esp,0x
    00A71021  xor eax,ea
    00A71023  retn
    
    

    1.4 以变量为判断条件的复杂if-else分支

    C源代码:

    两个连续的if-else判断,变量为判断条件

    int _tmain(int argc, _TCHAR* argv[])
    {
    	if (argc>0)
    	{
    		if (argc == 1)
    			printf("Hello!
    "); 
    		else
    			printf("Hello everybody!
    ");
    	}
    	else
    	{
    		if (argc == 1)
    			printf("World!
    "); 
    		else
    			printf("Hello everybody!
    ");
    	}
    
    	return 0;
    }
    
    
    

    Debug版本汇编:

    00E470DE cmp [arg.1],0x0    
    00E470E2 jle X9-10.00E47108 ; 最外层的if分支
    00E470E4 cmp [arg.1],0x1    
    00E470E8 jnz X9-10.00E470F9 ; 内层第一个if分支
    00E470EA push 9-10.00ED5E50 ;  
    00E470EF call 9-10.00E43CE7 ; 调用printf函数输出"Hello!
    00E470F4 add esp,0x4        
    00E470F7 jmp X9-10.00E47106 
    00E470F9 push 9-10.00ED5E5C ; 内层第一个else分支内容开始
    00E470FE call 9-10.00E43CE7 ; 调用printf函数输出"Hello everybody!
    00E47103 add esp,0x4        
    00E47106 jmp X9-10.00E4712A 
    00E47108 cmp [arg.1],0x1    ; 最外层的else分支
    00E4710C jnz X9-10.00E4711D ; 内层第二个if分支
    00E4710E push 9-10.00ED5E74 ; 
    00E47113 call 9-10.00E43CE7 ; 调用printf函数输出 "World!
    00E47118 add esp,0x4        
    00E4711B jmp X9-10.00E4712A 
    00E4711D push 9-10.00ED5E5C ; 内层第二个else分支
    00E47122 call 9-10.00E43CE7 ; 调用printf函数输出"Hello everybody!
    00E47127 add esp,0x4        
    00E4712A xor eax,eax        
    

    release版生成的代码:

    直接就是变量当成常量输出了,所有的流程只剩下会被执行的一部分汇编代码。

    00C91000 push ebp
    00C91001 mov ebp,esp
    00C91003 mov eax,[arg.1]
    00C91006 test eax,eax
    00C91008 jle X9-10.00C91020 ; 最外层的if-else分支
    00C9100A cmp eax,0x1
    00C9100D jnz X9-10.00C91020 ; 内层第一个if-else分支
    00C9100F push 9-10.00CA6448 ; 
    00C91014 call 9-10.printf   ; 调用printf函数输出"Hello!
    00C91019 add esp,0x4
    00C9101C xor eax,eax
    00C9101E pop ebp
    00C9101F retn
    00C91020 push 9-10.00CA6454 ; 
    00C91025 call 9-10.printf   ; 调用printf函数输出"Hello everybody!
    00C9102A add esp,0x4
    00C9102D xor eax,eax
    00C9102F pop ebp
    00C91030 retn
    
    

    1.5 识别三目运算符

    1. 有序常量的三目运算

    C源代码:

    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	return argc==1 ? 2:3;
    }
    
    

    Debug反汇编特征:

    Debug下的汇编与常见的if-else无异。

    0039773B cmp     dword ptr [ebp+0x8], 0x1 ;  比较
    0039773F jnz     short 0039774D           ;  分支跳转
    00397741 mov     dword ptr [ebp-0xC4], 0x2;  分支1内容:赋值2
    0039774B jmp     short 00397757           ;  无条件跳转:执行完分支1,就不在执行分支2
    0039774D mov     dword ptr [ebp-0xC4], 0x3;  分支2内容:赋值3
    00397757 mov     eax, dword ptr [ebp-0xC4]
    0039775D pop     edi                      ;  9-11.<ModuleEntryPoint>
    0039775E pop     esi
    

    Relese反汇编特征:

    00121272 cmp     dword ptr [ebp+0x8], 0x1;  比较
    00121276 setne   al                      ;  判断ZF标志位,判断是否对al赋值
    00121279 add     eax, 0x2                ;  将eax与2相加
    0012127C pop     ebp          
    
    

    setne这个指令含义为:

    if ZF=1 then cl=0
    if ZF=0 then cl=1
    

    程序最后的返回结果只能有两种,即2与3,而setne指令则会根据ZF位的影响来决定是给al(也可以理解为eax)赋值1还是0。

    如果比较相等,则eax的值会被置为0,加上2之后正好返回2;而如果不等的话自然就会返回3了。

    2. 稍复杂的有序常量的三目运算

    C源代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("11111111111111");
    	return argc == 1 ? 6:8;
    }
    
    

    Relese反汇编指令:

    00281272 cmp     dword ptr [ebp+0x8], 0x1
    00281276 setne   al
    00281279 lea     eax, dword ptr [eax*2+0x6]
    00281280 pop     ebp
    

    三目运算只可能等于6或8,因此编译器用了一个lea指令对其进行加法运算。

    如果条件为假,那么eax里的值为0,执行完lea指令后的最终结果为6;

    如果条件为真,那么eax里的值为1,执行完lea指令后的最终结果为8;

    有序常量的三目运算符特征如下:

    xor     reg32, reg32           ;任意可用的寄存器,但两个操作数所用的寄存器必须相同
    cmp     dword ptr [ebp+0x8],?? ;比较对比数与参照数
    setne   reg8                   ;XOR中所用到的寄存器(或其低位寄存器)
    
    

    3. 无序常量的三目运算

    C源代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("1111111111111111111111111");
    	return argc == 1 ? 68 : 6;
    }
    

    Relese反汇编指令:

    00C61270 mov     eax, 0x6                ;将参数传递给eax
    00C61275 cmp     dword ptr [ebp+0x8], 0x1;对比
    00C61279 mov     ecx, 0x44
    00C6127E cmove   eax, ecx                ;等于0的时候传送
    

    我用VS2015生成的代码和书上的反汇编指令是不一样的。
    cmove指令的含义是:

    cmove S, D //等于0时传送
    

    返回值会是6或68,先把6的值传到eax寄存器中。cmp对比后,下一条指令是把68的值传到ecx寄存器里。

    如果等于0,就用ecx寄存器值传送到eax寄存器中,否则不传送。

    4. 值为变量的三目运算

    C源代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("111111");
    	return argc == 1 ? 6:(int)argv;
    }
    

    Relese反汇编指令:

    00AC126D mov     eax, dword ptr [ebp+0xC];  将局部变量保存到eax中
    00AC1270 add     esp, 0x4                ;  平衡堆栈
    00AC1273 cmp     dword ptr [ebp+0x8], 0x1;  比较数值是否等于1
    00AC1277 mov     ecx, 0x6                ;  将分支1内容赋值到ecx
    00AC127C cmove   eax, ecx                ;  根据CMP影响的标志位结果判断是否传送
    00AC127F pop     ebp
    00AC1280 retn
    
    

    在以上C语言程序例子我用VS2015编译后,OD反汇编出来的指令与书上的指令还是不一样的。与无序常量的三目运算反汇编后的指令差不多。

    这说明VS版本编译器更新后,编译优化做了很大的改动。接下来的日子还是要通过不断地练习,才能让自己的水平真正的见长起来。

    小结:

    编译器之所以这样做是因为每执行一次寄存器赋值操作要比偶尔一次的流程转移操作更加高效,需要付出的代价更小。

    0x2 参考

    《黑客免杀攻防》 软件逆向工程(4)
    http://blog.csdn.net/dalerkd/article/details/41251595

  • 相关阅读:
    How to write an async method with out parameter?
    Common async / Task mistakes, and how to avoid them
    C# string reference type?
    Executing tasks in parallel
    SSIS How to Create an ETL Package Article 03/25/2022 3 minutes to read
    Should I worry about "This async method lacks 'await' operators and will run synchronously" warning
    C# 5.0 Async Tips and Tricks, Part 1
    What the async keyword actually does
    Why DataSet is being passed by reference without explicitly passing out or ref parameter? [duplicate]
    Windows权限维持
  • 原文地址:https://www.cnblogs.com/17bdw/p/7686962.html
Copyright © 2020-2023  润新知