zoukankan      html  css  js  c++  java
  • 【黑客免杀攻防】读书笔记9

    0x1 循环语句

    C语言的循环主要分为for、while与do-while 3种。

    1.1 do-while循环

    C源代码:

    0x41是字母A的ASCII码,变量nNum的初始值是26,因此0x41+(26-nNum)配合着每次的nNum--,是一个从字母A到Z的打印过程。

    #include "stdafx.h"
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	int nNum = 26;
    	printf("Mom! I can sing my ABC!
    ");
    
    	do  // 听!小Baby开始唱ABC了……
    	{
    		printf("%c ", 0x41+(26-nNum) );
    		nNum--;
    	} while (nNum>0);
    	return 0;
    }
    

    Debug反汇编代码:

    00D37710 push ebp
    ...省略代码
    00D3772E mov [local.2],0x1A ;  1A等于十进制的26,赋值到nNum变量中
    00D37735 push 00DC8E50      ;  Mom! I can sing my ABC!
    
    00D3773A call 00D33D14      ;  调用printf函数
    00D3773F add esp,0x4        ;  调用者进行堆栈平衡
    00D37742 mov eax,0x1A       ;  1A等于十进制的26
    00D37747 sub eax,[local.2]  ;  eax减去nNum变量当前的值,26-nNum
    00D3774A add eax,0x41       ;  41是十进制的A,与A相加
    00D3774D push eax           
    00D3774E push 00DC8E70      ;  %c
    00D37753 call 00D33D14      ;  调用printf函数
    00D37758 add esp,0x8        
    00D3775B mov eax,[local.2]  ;  把nNum变量赋值到eax
    00D3775E sub eax,0x1        ;  nNum变量减去1
    00D37761 mov [local.2],eax  ;  eax的值重新赋值回nNum变量,因为汇编是不可以直接对数值进行操作的,必须经过寄存器进行操作
    00D37764 cmp [local.2],0x0  ;  对比nNum变量的值是否为0
    00D37768 jg short 00D37742  ;  不满足条件跳转回原先循环的地址00D37742处重复执行
    ...省略代码
    00D3776E pop ebx
    
    

    Release反汇编代码:

    00D37710 push ebp
    ...省略代码
    00941261 push 009564D8    ; Mom! I can sing my ABC!
    
    00941266 call printf      ; 调用printf函数
    0094126B add esp,0x4      ; 调用者进行堆栈平衡
    0094126E mov esi,0x41     ; 1A等于十进制的26,赋值到esi寄存器中
    00941273 push esi         ; <%c> ;注意下这里
    00941274 push 009564F4    ; %c
    00941279 call printf      ; 调用printf函数
    0094127E inc esi          ; 直接将esi加1
    0094127F add esp,0x8      ; 平衡堆栈操作
    00941282 cmp esi,0x5B     ; 直接与0x5B相对比(Z的十六进制码是0x5A)
    00941285 jl short 00941273; 如果小于此值则跳转回00941273地址处再次执行
    ...省略代码
    00D3776E pop ebx
    

    通过以上代码,可以看出编译器已经把代码进行了优化:

    int main()
    {
        int nNum = 0x41;
        do{
            printf("%c",nNum++)
        }while(nNum<'[') //while(nNum < 0x5B)
        // z的十六进制是5A
    }
    

    编译器把稍显复杂的代码变得非常简单,真实意图是将我们的减法运算转换为加法运算。原因是CPU本质上只会做加法运算,因此加法运算对于CPU来说是执行速度最快的计算。

    特征:

    DO_TAG:
      XXXXX
      代码进行操作; do..while循环执行的内容
      XXXXX
      CMP XXX,XXX ; 对比条件
      JXX DO_TAG  ; 不满足跳转到原先执行的位置
    

    1.2 while循环

    C源代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	int nNum = 26;
    	printf("Mom! I can sing my ABC!
    ");
    
    	while(nNum>0)  // 听!小Baby开始唱ABC了……
    	{
    		printf("%c ",0x41+(26-nNum) );
    		nNum--;
    	}
    
    	return 0;
    }
    

    Debug反汇编代码:

    Debug版本的反汇编代码多了两条用于判断是否跳出循环的指令。因为do..while是先执行代码,后进行判断。while则是先进行判断,后执行代码。

    00BC7710 push ebp
    ..省略代码
    00BC772C rep stos dword ptr es:[edi]
    00BC772E mov [local.2],0x1A  ;  把26赋值到nNum变量中
    00BC7735 push 00C58E50       ;  Mom! I can sing my ABC!
    
    00BC773A call 00BC3D14
    00BC773F add esp,0x4
    00BC7742 cmp [local.2],0x0
    00BC7746 jle short 00BC776C  ;  多了个判断,如果其小于等于0,则跳出循环
    00BC7748 mov eax,0x1A        ;  把26赋值到eax
    00BC774D sub eax,[local.2]   ;  用存着26的eax寄存器减去nNum变量
    00BC7750 add eax,0x41        ;  跟0x41(A)相加
    00BC7753 push eax
    00BC7754 push 00C58E70       ;  %c
    00BC7759 call 00BC3D14       ;  调用printf函数
    00BC775E add esp,0x8         ;  平衡堆栈
    00BC7761 mov eax,[local.2]
    00BC7764 sub eax,0x1         ;  前后两行代码都是nNum变量减去1
    00BC7767 mov [local.2],eax
    00BC776A jmp short 00BC7742  ;  无条件跳转回判断00BC7742 处
    00BC776C xor eax,eax
    ..省略代码
    00BC7781 retn
    
    

    Release反汇编代码:

    do..while与while生成的Release版代码是相同的。编译器探测出了我们的循环判断用的是一个常量,因此不存在首次执行条件不匹配的情况。

    002B1260 push esi         ;  9-16.__argc
    002B1261 push 002C64D8    ;  Mom! I can sing my ABC!
    
    002B1266 call printf      ;  调用printf
    002B126B add esp,0x4      ;  平衡堆栈
    002B126E mov esi,0x41     ;  把0x41赋值到esi
    002B1273 push esi         ;  <%c>
    002B1274 push 002C64F4    ;  %c
    002B1279 call printf      ;  调用printf
    002B127E inc esi          ;  加1指令
    002B127F add esp,0x8      ;  平衡堆栈
    002B1282 cmp esi,0x5B     ;  对比0x5B
    002B1285 jl short 002B1273;  小于0x5B则跳转
    002B1287 xor eax,eax
    002B1289 pop esi
    002B128A retn
    

    将代码中的常量改成变量试试,那么汇编代码就会更改成另外一种形式了。C源代码如下:

    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("Mom! I can sing my ABC!
    ");
    
    	while(argc>0)  // 听!小Baby开始唱ABC了……
    	{
    		printf("%c ",0x41+(26-argc) ); //输出Z
    		argc--;
    	}
    
    	return 0;
    }
    

    Release版的反汇编代码:

    00041260 push ebp
    ...省略代码
    00041264 push 000564D8     ;  Mom! I can sing my ABC!
    
    00041269 call printf       ;  printf
    0004126E mov esi,[arg.1]   ;  将参数赋值到esi寄存器中
    00041271 add esp,0x4       ;  平衡堆栈
    00041274 test esi,esi      ;  测试参数是否为0
    00041276 jle short 00041295;  如果为0则跳出循环
    00041278 push edi
    00041279 mov edi,0x5B      ;  0x5B = 0x41+26
    0004127E sub edi,esi       ;  用0x5B减去参数
    00041280 push edi          ;  <%c> 此时的值由5B变成了5A,也就是'Z'
    00041281 push 000564F4     ;  %c
    00041286 call printf       ;  printf
    0004128B dec esi           ;  参数减1
    0004128C add esp,0x8       ;  平衡堆栈
    0004128F inc edi           ;  EDI加1,5A又变回5B
    00041290 test esi,esi      ;  按位的'与'运算,指令对两个操作数 的内容均不进行修改,仅是在逻辑与操作后,对标志位重新置位
    00041292 jg short 00041280 ;  如果参数大于ESI时则继续循环,否则就向下执行
    ...省略代码
    00041299 retn
    
    

    用变量做判断条件很明显与常量不一样,编译器将0x41+(26-argc)优化成了0x5B-argc。如下:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("Mom! I can sing my ABC!
    ");
    
    	//while(argc>0)  // 听!小Baby开始唱ABC了……
    	//{
    	//	printf("%c ",0x41+(26-argc) ); //输出Z
    	//	argc--;
    	//}
    
    	while (argc>0) 
    	{
    		printf("%c", 0x5B - argc);  //直接就是0x5B-参数
    		argc--;
    		0x5B + 1;
    
    	}
    
    	return 0;
    }
    

    while循环反汇编特征:

    先对比在执行代码,不满足条件再次循环

        CMP XXX,XXX  ;注:CMP可以替换为TEST
        JXX WHILE_END_TAG
    WHILE_TAG:
        .......
    	.......
    	CMP XXX,XXX
    	JXX WHILE_TAG
    WHILE_END_TAG:
    

    1.3 for循环

    for循环与while循环本质上是一样的,唯一的不同在于for循环在循环体内多了一个步长部分。

    C源代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("Mom! I can sing my ABC!
    ");
    
    	// 听!小Baby开始唱ABC了……
    	for (int nNum = 26; nNum>0; nNum--)
    		printf("%c ",0x41+(26-nNum) );
    
    	return 0;
    }
    
    

    Debug反汇编代码:

    
    0003C540 push ebp
    ...省略代码
    0003C55C rep stos dword ptr es:[edi]
    0003C55E push 9-19.00090C70         ;  Mom! I can sing my ABC!
    
    0003C563 call 9-19.0003B0B3         
    0003C568 add esp,0x4                ;  平衡堆栈
    0003C56B mov [local.2],0x1A         ;  1A等于十六进制26,赋值给nNum变量中
    0003C572 jmp X9-19.0003C57D         
    0003C574 mov eax,[local.2]          ;  nNum被赋值到eax寄存器
    0003C577 sub eax,0x1                ;  nNum--;步长的位置
    0003C57A mov [local.2],eax
    0003C57D cmp [local.2],0x0          ;  判断是否大于0
    0003C581 jle X9-19.0003C59E         ;  如果小于0就结束循环
    0003C583 mov eax,0x1A
    0003C588 sub eax,[local.2]          ;  26-nNum变量
    0003C58B add eax,0x41               ;  跟0x41(A)相加
    0003C58E push eax
    0003C58F push 9-19.00090C6C         ;  %c
    0003C594 call 9-19.0003B0B3         ;  调用Printf()函数
    0003C599 add esp,0x8
    0003C59C jmp X9-19.0003C574
    0003C59E xor eax,eax
    ...省略代码
    0003C5B2 pop ebp
    0003C5B3 retn
    
    
    • 显然从循环语句这个集合是从do-while先诞生的,而后是while,最后才是for。
    • 从执行效率上看,代码最短且判断最少的就是do-while循环了。

    Release版反汇编:

    00301000 push esi
    00301001 push 9-19.00316448; Mom! I can sing my ABC!
    
    00301006 call 9-19.printf  ; printf
    0030100B add esp,0x4
    0030100E mov esi,0x41      ;  将0x41 赋值给 esi
    00301013 push esi          ; <%c>
    00301014 push 9-19.00316464; %c
    00301019 call 9-19.printf  ; printf
    0030101E inc esi           ;  esi自增1
    0030101F add esp,0x8
    00301022 cmp esi,0x5B      ;  对比是否小于0x5B,Z等于0x5A
    00301025 jl X9-19.00301013
    00301027 xor eax,eax
    00301029 pop esi
    0030102A retn
    

    由于本例子中使用了常量,基本逻辑没有改变,所以这段代码与do-while、while一模一样。

    印证了while与for都是以do-while为基础框架的,只不过是在里面加了一些小判断。

    变量版for循环的反汇编例子:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	printf("Mom! I can sing my ABC!
    ");
    
    	// 听!小Baby开始唱ABC了……
    	for ( ; argc >0; argc--)
    		printf("%c ",0x41+(26-argc) );
    
    
    	return 0;
    }
    

    Release版反汇编代码:

    以变量为判断条件的for循环与while循环所生成的代码是完全相同的。

    00391000 push edi
    00391001 push 9-20.0039B384            ; /Mom! I can sing my ABC!
    
    00391006 call 9-20.printf              ; printf
    0039100B mov edi,dword ptr ss:[esp+0xC];  取得参数,argc
    0039100F add esp,0x4
    00391012 test edi,edi
    00391014 jle X9-20.00391035            ;  如果参数为空就跳出循环
    00391016 push esi
    00391017 mov esi,0x5B                  ;  将0x5B赋值给esi,Z的值是0x5A
    0039101C sub esi,edi                   ;  0x5B的值减去参数值argc
    0039101E mov edi,edi
    00391020 push esi                      ; /<%c>
    00391021 push 9-20.0039B3A0            ; |%c
    00391026 call 9-20.printf              ; printf
    0039102B dec edi                       ;  edi减1
    0039102C add esp,0x8                   ;  平衡堆栈
    0039102F inc esi                       ;  esi自增1
    00391030 test edi,edi
    00391032 jg X9-20.00391020
    00391034 pop esi                       ;  9-20.<ModuleEntryPoint>
    00391035 xor eax,eax
    00391037 pop edi
    00391038 retn
    

    Debug版本for循环的一个反汇编特点的总结:

    首先对初始化变量进行赋值,判断条件是否符合。不符合则不跳转继续执行代码块内容。然后无条件跳转回前面的地址,执行增加步长部分的指令。伪代码如下:

    初始化块:
        jmp CMP_TAG
    STEP_TAG:
        步长块执行
    CMP_TAG:
        for中的条件判断,判断是否结束循环
    	JXX FOR_END_TAG
    	.....
    	执行for代码块中的内容
    	.....
    	jmp STEP_TAG ;无条件跳转回步长操作部分
    	.....
    	FOR_END_TAG:
    

    总结:

    debug版3种循环各不相同,Release版可总结如下:

    • 当循环采用常量为判断条件时,相同逻辑的3种循环生成的代码完全相同。
    • 当循环采用变量为判断条件时,相同逻辑的while与for生成的代码完全相同,而do-while则自成一格。
  • 相关阅读:
    Python
    Kubernetes之二workload资源编排
    Kubernetes之一安装
    DockerFile
    Docker的安装和使用
    Elastic Stack配置和使用
    虚拟化KVM应用
    Tomcat安装和使用
    Keepalived实现双主模型的ipvs高可用集群+实现双主模型的nginx高可用集群
    实验lvs+dns+nfs+mysql+web
  • 原文地址:https://www.cnblogs.com/17bdw/p/7707570.html
Copyright © 2011-2022 走看看