[3] 以逆向的角度来看循环语句——do、while、for的比较

[3] 以逆向的角度来看循环语句——do、while、for的比较

1. do循环

​ 先执行循环体,后比较判断

#include <stdio.h>
int main(int argc, char* argv[]) {
  int sum = 0;
  int i = 0; 
  do {
    sum += i; 
    i++;
  }
  while(i <= argc); 
  return sum;
}

汇编标识:

;x86_vs
00401000  push    ebp
00401001  mov     ebp, esp
00401003  sub     esp, 8
00401006  mov     dword ptr [ebp-8], 0         ;sum=0
0040100D  mov     dword ptr [ebp-4], 0         ;i=0 
{
00401014  mov     eax, [ebp-8]                 ;do_while语句块代码
00401017  add     eax, [ebp-4]
0040101A  mov     [ebp-8], eax                 ;sum+=i
0040101D  mov     ecx, [ebp-4]
00401020  add     ecx, 1
00401023  mov     [ebp-4], ecx                 ;i+=1 
}00401026  mov     edx, [ebp-4]
00401029  cmp     edx, [ebp+8]
0040102C  jle     short loc_401014             ;如果i<=argc,跳转到do_while语句块代码
0040102E  mov     eax, [ebp-8]                 ;do_while结束代码块
00401031  mov     esp, ebp
00401033  pop     ebp
00401034  retn

;x86_gcc
00401510  push    ebp
00401511  mov     ebp, esp
00401513  and     esp, 0FFFFFFF0h
00401516  sub     esp, 10h
00401519  call    ___main
0040151E  mov     dword ptr [esp+0Ch], 0       ;sum=0
00401526  mov     dword ptr [esp+8], 0         ;i=0 
{
0040152E  mov     eax, [esp+8]                 ;do_while语句块代码
00401532  add     [esp+0Ch], eax               ;sum+=i
00401536  add     dword ptr [esp+8], 1         ;i+=1
0040153B  mov     eax, [esp+8]
0040153F  cmp     eax, [ebp+8]
00401542  jg      short loc_401546             ;如果i>argc,跳转到do_while结束代码块
}
00401544  jmp     short loc_40152E             ;跳转到do_while语句块代码
00401546  mov     eax, [esp+0Ch]               ;do_while结束代码块

逆向总结:

​ x86_vs:while语句的比较数是相同的

{
DO_WHILE语句代码块
...
}
执行影响标志位指令
JXX跳转到DO_WHILE语句代码块

​ x86_gcc:while语句的比较数是相反的

{
DO_WHILE语句代码块
执行影响标志位指令
JXX跳转到DO_WHILE结束代码块
...
}
跳转到DO_WHILE语句代码块
DO_WHILE结束代码块

2. while循环:先比较判断,后执行循环体

#include <stdio.h>
int main(int argc, char* argv[]) {
  int sum = 0;
  int i = 0;
  while(i <= argc)  { 
    sum += i;
    i++; 
  }
  return sum; 
}

汇编标识:

00401000  push    ebp
00401001  mov     ebp, esp
00401003  sub     esp, 8
00401006  mov     dword ptr [ebp-8], 0        ;sum=0
0040100D  mov     dword ptr [ebp-4], 0        ;i=0 
{
00401014  mov     eax, [ebp-4]                ;while语句代码块
00401017  cmp     eax, [ebp+8]
0040101A  jg      short loc_401030            ;如果i>argc,则跳转到while结束代码块
0040101C  mov     ecx, [ebp-8]
0040101F  add     ecx, [ebp-4]
00401022  mov     [ebp-8], ecx                ;sum+=i
00401025  mov     edx, [ebp-4]
00401028  add     edx, 1
0040102B  mov     [ebp-4], edx                ;i+=1 
}
0040102E  jmp     short loc_401014            ;跳转到while语句代码块
00401030  mov     eax, [ebp-8]                ;while结束代码块

注意:

​ while循环结构中使用了两次跳转指令完成循环,因为多使用了一次跳转指令,所以while循环比do循环效率低一些

​ while循环结构很可能被优化成do循环结构,被转换后的while结构需要检查是否可以被成功执行一次,通常会被嵌套在if单分支结构中

逆向总结:

{
while语句代码块
执行影响标志位指令
JXX跳转到while结束代码块
...
}
JMP跳转到while语句代码块
while结束代码块

3. for循环:先初始化,再比较判断,最后执行循环体

#include <stdio.h>
int main(int argc, char* argv[]) { 
  int sum = 0;
  for (int i = 0; i <= argc ; i++) { 
    sum += i;
  }
  return sum; 
}

汇编标识:

00401006  mov     dword ptr [ebp-8], 0         ;sum=0 
{
0040100D  mov     dword ptr [ebp-4], 0         ;赋初值语句代码块,i=0
}
00401014  jmp     short loc_40101F             ;跳转到for语句代码块
{
00401016  mov     eax, [ebp-4]                 ;步长语句代码块
00401019  add     eax, 1
0040101C  mov     [ebp-4], eax                 ;i+=1 
}
{
0040101F  mov     ecx, [ebp-4]                 ;for语句代码块
00401022  cmp     ecx, [ebp+8]
00401025  jg      short loc_401032             ;如果i>argc,则跳转到for结束语句块
00401027  mov     edx, [ebp-8]
0040102A  add     edx, [ebp-4]
0040102D  mov     [ebp-8], edx                 ;sum+=i}
00401030  jmp     short loc_401016             ;跳转到步长语句代码块
00401032  mov     eax, [ebp-8]                 ;for结束语句块

逆向总结:

赋初值语句代码块
JMP跳转到for语句代码块
{
步长语句代码块
...
}
{
for语句代码块
执行影响标志位的指令
JXX跳转到for结束语句块
...
}
JMP跳转到步长语句代码块
for结束语句块

​ 计数器变量被赋初值后,利用jmp跳过第一次步长计算。然后,通过3个跳转指令还原for循环的各个组成部分:

​ 1)第一个jmp指令之前的代码为初始化部分;

​ 2)从第一个jmp指令到循环条件比较处之间的代码为步长计算部分;

​ 3)在条件跳转指令jxx之后寻找向上跳转的jmp指令,且其目标是到步长计算的位置,在jxx和这个jmp指令之间的代码即为循环语句块

4. 三种循环结构的效率比较

Release优化

​ 1)do循环效率最高

​ 2)while循环执行两次JMP跳转,效率低于do循环,可将其优化为if嵌套的do循环提高效率

int LoopWhile(int count){
  int sum = 0;
  int i = 0;  if(count >= 0){ 
    do {
      sum += i; 
      i++; 
    }
    while(i <= count) 
  }
  return sum; 
}

​ 3)for循环执行三次JMP跳转,效率最低,可将其优化为if嵌套的do循环提高效率

posted @ 2023-02-03 02:17  修竹Kirakira  阅读(37)  评论(0编辑  收藏  举报