首页
社区
课程
招聘
[原创]从反汇编的角度学C/C++之循环结构
2021-9-29 11:37 8552

[原创]从反汇编的角度学C/C++之循环结构

2021-9-29 11:37
8552

    在C/C++中,我们有三种循环结构。分别是while,do-while,for。下面的代码中,三种循环结构所做的事情是一样,通过比较这三种情况下的生成的汇编,我们就可以分析得出三种循环结构在内存中的表现形式得不同点

int main()
{
	int count = 0;

	count = 0;
	while (count < 10)
	{
		printf("while...\n");
		count++;
	}
	printf("while over\n");

	count = 0;
	do
	{
		printf("do while...\n");
		count++;
	} while (count < 10);
	printf("do while over\n");

	for (count = 0; count < 10; count++)
	{
		printf("for...\n");
	}
	printf("for over\n");
	

	return 0;
}

一.while循环

	count = 0;
00E110EF  mov         dword ptr [count],0      //为count赋值为0
	while (count < 10)
00E110F6  cmp         dword ptr [count],0Ah    //将count得值与0xA也就是10做比较    
00E110FA  jge         main+54h (0E11114h)      //若大于等于则跳出循环
	{
		printf("while...\n");
00E110FC  push        offset string "while...\n" (0E831B0h)      //符合条件执行循环语句内容
00E11101  call        printf (0E111A0h)  
00E11106  add         esp,4  
		count++;
00E11109  mov         eax,dword ptr [count]      //把count值赋给eax
00E1110C  add         eax,1                      //eax加1
00E1110F  mov         dword ptr [count],eax      //在把eax赋值给count实现count++
	}
00E11112  jmp         main+36h (0E110F6h)        //跳转到count与0xA比较得地方
	printf("while over\n");
00E11114  push        offset string "while over\n" (0E831BCh)  //while循环之外得代码
00E11119  call        printf (0E111A0h)  
00E1111E  add         esp,4

    由此可以看到while循环中,我们先是判断是否满足条件,不满足得话会跳出循环,如果满足执行相应得指令以后在跳转到判断语句处进行执行。执行得流程图如下图所示:

二.do-while循环

	count = 0;
00E11121  mov         dword ptr [count],0                      //为count赋值为0
	do
	{
		printf("do while...\n");
00E11128  push        offset string "do while...\n" (0E831C8h)      //执行循环条件成立的代码
00E1112D  call        printf (0E111A0h)  
00E11132  add         esp,4  
		count++;
00E11135  mov         eax,dword ptr [count]  
00E11138  add         eax,1  
00E1113B  mov         dword ptr [count],eax                     //实现count+1的操作
	} while (count < 10);
00E1113E  cmp         dword ptr [count],0Ah                     //将count与10做比较
00E11142  jl          main+68h (0E11128h)                       //如果小于10则跳转到循环开始处
	printf("do while over\n");
00E11144  push        offset string "do while over\n" (0E831D8h)  
00E11149  call        printf (0E111A0h)  
00E1114E  add         esp,4

    可以看到do-while循环中,我们先是执行循环条件成立时候的代码,然后在进行判断,如果符合条件则跳转回去继续执行。流程图如下

三.for循环

	for (count = 0; count < 10; count++)
00E11151  mov         dword ptr [count],0          //为count赋值为0
00E11158  jmp         main+0A3h (0E11163h)         //跳转到判断语句执行
00E1115A  mov         eax,dword ptr [count]        
00E1115D  add         eax,1                        
00E11160  mov         dword ptr [count],eax        //将count的值+1  
00E11163  cmp         dword ptr [count],0Ah        //将count的值与10做比较
00E11167  jge         main+0B8h (0E11178h)         //如果大于则跳转到循环体外
	{
		printf("for...\n");
00E11169  push        offset string "for...\n" (0E831E8h)  //执行条件成立的代码
00E1116E  call        printf (0E111A0h)  
00E11173  add         esp,4  
	}
00E11176  jmp         main+9Ah (0E1115Ah)          //跳转到count值加1的代码执行
	printf("for over\n");
00E11178  push        offset string "for over\n" (0E831F0h)  
00E1117D  call        printf (0E111A0h)  
00E11182  add         esp,4

    在for循环中,先执行第一个分号前的赋值指令,然后就跳转到判断指令进行判断,如果满足条件则执行条件成立指令执行随后在跳转到第二个分号后的语句执行,这个语句继续向下执行会继续判断条件是否成立。流程图如下

四.for与数组及指针

    由于for循环经常和数组指针一起使用,所以下面给出一个例子来看看汇编中的表现形式

	int arrTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
00F910E8  mov         dword ptr [arrTest],0  
00F910EF  mov         dword ptr [ebp-28h],1  
00F910F6  mov         dword ptr [ebp-24h],2  
00F910FD  mov         dword ptr [ebp-20h],3  
00F91104  mov         dword ptr [ebp-1Ch],4  
00F9110B  mov         dword ptr [ebp-18h],5  
00F91112  mov         dword ptr [ebp-14h],6  
00F91119  mov         dword ptr [ebp-10h],7  
00F91120  mov         dword ptr [ebp-0Ch],8  
00F91127  mov         dword ptr [ebp-8],9      //为数组进行初始化赋值
	int *pTest = arrTest;
00F9112E  lea         eax,[arrTest]  
00F91131  mov         dword ptr [pTest],eax    //将数组地址赋值给指针
	
	for (int i = 0; i < 10; i++)
00F91134  mov         dword ptr [ebp-44h],0         //对i进行初始化,此时地址ebp-0x44所保存的就是i的值
00F9113B  jmp         main+86h (0F91146h)           //跳转到条件判断
00F9113D  mov         eax,dword ptr [ebp-44h]      
00F91140  add         eax,1  
00F91143  mov         dword ptr [ebp-44h],eax       //对i进行加1操作
00F91146  cmp         dword ptr [ebp-44h],0Ah       //判断i是否大于10
00F9114A  jge         main+0ADh (0F9116Dh)          //大于10则跳出循环
	{
		printf("arrTest[i]=%d,*(pTest+i)=%d\n", arrTest[i], *(pTest + i));
00F9114C  mov         eax,dword ptr [ebp-44h]       //取出i的值赋给eax
00F9114F  mov         ecx,dword ptr [pTest]         //取出pTest的值,此时这个值是arrTest的首地址
00F91152  mov         edx,dword ptr [ecx+eax*4]     //根据首地址与i*4的偏移得到要用到的数的地址
00F91155  push        edx                          
00F91156  mov         eax,dword ptr [ebp-44h]       //得到i的值
00F91159  mov         ecx,dword ptr [arrTest+eax*4]  //根据i的值与数组首地址从数组中取出数据  
00F9115D  push        ecx  
00F9115E  push        offset string "arrTest[i]=%d,*(pTest+i)=%d\n" (010031B0h)  
00F91163  call        printf (0F911B0h)  
00F91168  add         esp,0Ch  
	}
00F9116B  jmp         main+7Dh (0F9113Dh)          //跳转到i++的语句执行

    由上可知,数组和指针的差别不大,只不过指针运算会多一次取出地址的操作。另外由于这里没用对下标进行判断是否超过了数组的界限,只是根据下标算偏移而得出地址,所以对于新手来说往往会犯的一个错误就是写出了如下的代码导致了程序运行错误。

int main()
{
	int i = 0;
	int arrTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	for (i = 1; i <= 10; i++)
	{
		arrTest[i] = 0;
	}

	return 0;
}

    这段代码本身是想给arrTest里面的元素赋值为0。但是因为for循环的时候没有用对下标导致了访问了数组之外的地址,而这个地址刚好是i的地址,导致当i等于10的时候,为arrTest[i]赋值的时候就是给i赋值。这就导致了程序的无限循环。反汇编结果如下:

6:        int i = 0;
00401028 C7 45 FC 00 00 00 00 mov         dword ptr [ebp-4],0        //为i赋值为1,此时ebp-4就是i的地址
7:        int arrTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
0040102F C7 45 D4 00 00 00 00 mov         dword ptr [ebp-2Ch],0
00401036 C7 45 D8 01 00 00 00 mov         dword ptr [ebp-28h],1
0040103D C7 45 DC 02 00 00 00 mov         dword ptr [ebp-24h],2
00401044 C7 45 E0 03 00 00 00 mov         dword ptr [ebp-20h],3
0040104B C7 45 E4 04 00 00 00 mov         dword ptr [ebp-1Ch],4
00401052 C7 45 E8 05 00 00 00 mov         dword ptr [ebp-18h],5
00401059 C7 45 EC 06 00 00 00 mov         dword ptr [ebp-14h],6
00401060 C7 45 F0 07 00 00 00 mov         dword ptr [ebp-10h],7
00401067 C7 45 F4 08 00 00 00 mov         dword ptr [ebp-0Ch],8
0040106E C7 45 F8 09 00 00 00 mov         dword ptr [ebp-8],9        //为数组初始化,此时ebp-0x2C就是数组首地址
8:
9:        for (i = 1; i <= 10; i++)
00401075 C7 45 FC 01 00 00 00 mov         dword ptr [ebp-4],1        //为i赋值为1
0040107C EB 09                jmp         main+77h (00401087)        //跳转到比较指令
0040107E 8B 45 FC             mov         eax,dword ptr [ebp-4]    
00401081 83 C0 01             add         eax,1
00401084 89 45 FC             mov         dword ptr [ebp-4],eax      //实现i++
00401087 83 7D FC 0A          cmp         dword ptr [ebp-4],0Ah      //判断是否大于10
0040108B 7F 0D                jg          main+8Ah (0040109a)        //大于则跳出循环结构
10:       {
11:           arrTest[i] = 0;
0040108D 8B 4D FC             mov         ecx,dword ptr [ebp-4]      //取出i的地址
00401090 C7 44 8D D4 00 00 00 mov         dword ptr [ebp+ecx*4-2Ch],0    //根据数组首地址与ecx*4的值算出地址赋值为0
12:       }
00401098 EB E4                jmp         main+6Eh (0040107e)        //跳到i++的指令执行

    当i等于10的时候我们可以看看ebp-4与ebp+ecx*4-0x2C的值。

    可以看到两者的值是相等的,也就是说执行

mov         dword ptr [ebp+ecx*4-2Ch],0

    和执行

mov         dword ptr [ebp-4],0

    是相同的,而ebp-4此时正是i的地址,i被赋值为0,这就导致了无线循环。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2021-10-20 11:19 被1900编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 997
活跃值: (1518)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
WMBa0 2023-1-26 10:45
2
0
5
游客
登录 | 注册 方可回帖
返回