在逆向分析中,循环语句通常会以特定的汇编模式或结构体现出来。常见的循环语句包括 for 循环、while 循环和 do-while 循环。由于不同的编译器会根据代码优化的级别生成不同的汇编代码,分析循环的模式也可能会有所不同。以下是三种常见循环语句的汇编分析要点:
1. for循环的逆向分析
典型的 C 代码:
for (int i = 0; i < n; i++) {
    // 循环体
}汇编特征:
初始化:i = 0 通常表现为一个寄存器赋初始值。 条件检查:比较寄存器与终止条件(如 n),可能使用跳转指令如 JLE、JGE。 循环体:位于条件检查后的指令块。 递增:通过 ADD 或 INC 操作来递增寄存器的值。
汇编示例:
mov     eax, 0          ; 初始化 i = 0
cmp     eax, n          ; 比较 i 和 n
jge     end_loop        ; 如果 i >= n,跳转到 end_loop
loop_body:              ; 循环体开始
; (循环体指令)
add     eax, 1          ; 递增 i
cmp     eax, n          ; 再次比较 i 和 n
jl      loop_body       ; 如果 i < n,跳回到 loop_body
end_loop:               ; 结束逆向分析技巧:
找到初始化的代码片段,如给寄存器赋值的操作;寻找条件比较部分,通常使用 CMP 或 TEST,接着跟踪跳转指令(如 JLE, JL),确定循环的边界。递增操作(ADD, INC)通常位于循环体的末尾或条件检查前。
2.while 循环的逆向分析
典型的 C 代码:
while (i < n) {
    // 循环体
}汇编特征:
条件检查:在进入循环体之前,首先比较寄存器的值,若不满足条件则直接跳出循环。 循环体:条件检查通过后,执行循环体代码。 递增:递增通常在循环体内完成,然后再次检查条件。
汇编示例:
cmp     eax, n          ; 比较 i 和 n
jge     end_while       ; 如果 i >= n,跳转到 end_while
while_body:             ; 循环体开始
; (循环体指令)
add     eax, 1          ; 递增 i
cmp     eax, n          ; 比较 i 和 n
jl      while_body      ; 如果 i < n,跳回到 while_body
end_while:              ; 结束当条件和索引的初始值都确定的情况下,编译器可以判断第一次会不会执行
逆向分析技巧:
条件检查通常位于循环体之前,通过 CMP 和跳转指令控制,循环体代码位于条件检查之后,跟踪跳转位置可以定位循环体的结束。确定计数器或条件变量的更新位置,并检查跳转逻辑。
3.do-while 循环的逆向分析
典型的 C 代码:
do {
    // 循环体
} while (i < n);汇编特征:
循环体:无条件执行一次循环体。 条件检查:循环体执行后检查条件,决定是否跳回循环。 跳转:条件满足时跳回到循环体的起始处。
汇编示例:
do_while_body:          ; 循环体开始
; (循环体指令)
add     eax, 1          ; 递增 i
cmp     eax, n          ; 比较 i 和 n
jl      do_while_body   ; 如果 i < n,跳回到 do_while_body逆向分析技巧:
do-while 循环的特点是循环体在条件检查之前执行,因此逆向时首先识别无条件执行的代码块,条件检查位于循环体之后,查看 CMP 或 TEST 等操作判断是否跳回;循环体通常使用跳转指令(如 JL, JG)回到循环体开头。
逆向分析示例
下面是一个包含 for 循环、while 循环 和 do-while 循环的 C 代码示例,我们可以将它编译成可执行文件,使用IDA 或 x64dbg进行逆向分析,观察它们对应的汇编代码差异。
#include <stdio.h>
int main() {
    int sum_for = 0;
    int sum_while = 0;
    int sum_do_while = 0;
    // for 循环
    for (int i = 0; i < 5; i++) {
        sum_for += i;
    }
    // while 循环
    int j = 0;
    while (j < 5) {
        sum_while += j;
        j++;
    }
    // do-while 循环
    int k = 0;
    do {
        sum_do_while += k;
        k++;
    } while (k < 5);
    printf("sum_for: %d\n", sum_for);
    printf("sum_while: %d\n", sum_while);
    printf("sum_do_while: %d\n", sum_do_while);
    system("pause");
    return 0;
}此时使用Visual Studio对该代码进行编译,生成exe文件,对应的编译配置为Debug-x86;本文只针对Debug-x86程序进行分析,其他编译配置分析方式也大同小异。

静态分析:
将生成的程序载入IDA中进行静态分析

在为了不模糊重点,我们直接在Functon Window中定位main函数(关于定位main函数的各种方法有兴趣请查看前面的文章)。

接下去开始逐步对进行代码分析,正文代码从后线以下开始:

首先代码先初始化了四个局部变量var_8、var_14、var_20、var_2C,并将其值全部设置为0。
mov     [ebp+var_8], 0
mov     [ebp+var_14], 0
mov     [ebp+var_20], 0
mov     [ebp+var_2C], 0
jmp     short loc_41185F在初始化变量后进行了跳转,跳转的目标地址为loc_41185F。

①for循环
可以看到在跳转到loc_41185F时,中间跳过了三条指令,这三条指令做的就是自增的操作,我们就可以通过这个特征就判断出这段代码就是for循环结构(该特征是for循环和while循环最明显的区别),接着我们来解析以下这个代码:
loc_411856:                             ; CODE XREF: _main+5E↓j
                mov     eax, [ebp+var_2C]
                add     eax, 1
                mov     [ebp+var_2C], eax
loc_41185F:                             ; CODE XREF: _main+44↑j
                cmp     [ebp+var_2C], 5
                jge     short loc_411870
                mov     eax, [ebp+var_8]
                add     eax, [ebp+var_2C]
                mov     [ebp+var_8], eax
                jmp     short loc_411856跳转至loc_41185F后:cmp [ebp+var_2C], 5局部变量var_2C先于5进行比较(可以看出来5就是这个循环的边界值),jge short loc_411870若局部变量var_2C大于等于5则跳转到loc_411870地址(也就是跳出循环),若不大于则继续往下执行。 mov eax, [ebp+var_8]将var_8的值存入寄存器eax,add eax, [ebp+var_2C]将局部变量的var_2C的值于eax中的值相加,mov [ebp+var_8], eax再将eax中存储的两数之和存储至var_8中。jmp short loc_411856最后跳转至loc_411856地址处执行局部变量var_2C自增代码。自增完成后再执行loc_41185F处指令。
总结:
初始化:var_2C 初始化为 0。 条件检查:每次循环开始时,比较 var_2C 是否小于 5。 递增计数器:在每次循环结束时,var_2C 增加 1。 累加操作:每次循环中,将 var_2C 的值加到 var_8 中。 循环终止:当 var_2C >= 5 时,跳出循环。
伪代码如下:
for(int var_2C = 0;var_2C < 5;var_2C++);
{
    var_8 += var_2C;
}②while循环
根据上述代码可知第一个循环结束后跳出循环,来到地址loc_411870进行执行,代码如下:

loc_411870:                             ; CODE XREF: _main+53↑j
                mov     [ebp+var_38], 0
loc_411877:                             ; CODE XREF: _main+7F↓j
                cmp     [ebp+var_38], 5
                jge     short loc_411891
                mov     eax, [ebp+var_14]
                add     eax, [ebp+var_38]
                mov     [ebp+var_14], eax
                mov     eax, [ebp+var_38]
                add     eax, 1
                mov     [ebp+var_38], eax
                jmp     short loc_411877mov [ebp+var_38], 0将局部变量 var_38 初始化为 0,这个变量可能是用作循环计数器。
cmp [ebp+var_38], 5比较 var_38 的值和 5,var_38 是循环计数器。
jge short loc_411891如果 var_38 的值大于或等于 5,则跳转到 loc_411891,这意味着循环结束(跳出循环)。
mov eax, [ebp+var_14]将局部变量 var_14 的值加载到 eax 寄存器中。var_14 可能是一个用于累加的变量。
add eax, [ebp+var_38]将 var_38(循环计数器)的值加到 eax 中。
mov [ebp+var_14], eax将累加后的结果存回 var_14,即更新了累加器。
mov     eax, [ebp+var_38]
add     eax, 1
mov     [ebp+var_38], eax后面三条指令就是对var_38局部变量进行自增的操作。
最后jmp short loc_411877无条件跳转到 loc_411877,重新执行循环体,继续下一次迭代。
这段代码是一个典型的 while循环,其中 var_38 是一个循环计数器,从 0 开始,直到计数器达到 5 时结束循环。循环中,var_38 的值不断累加到 var_14 中。
伪代码表示如下:
int var_38 = 0;  // 计数器初始化
while (var_38 < 5) {
    var_14 += var_38;  // 累加操作
    var_38++;  // 计数器递增
}③do-while循环
第二个循环结束后,根据上述代码可知跳入第三个循环loc_411891(红线以上部分):

loc_411891:                             ; CODE XREF: _main+6B↑j
                mov     [ebp+var_44], 0
loc_411898:                             ; CODE XREF: _main+9E↓j
                mov     eax, [ebp+var_20]
                add     eax, [ebp+var_44]
                mov     [ebp+var_20], eax
                mov     eax, [ebp+var_44]
                add     eax, 1
                mov     [ebp+var_44], eax
                cmp     [ebp+var_44], 5
                jl      short loc_411898mov [ebp+var_44], 0始化局部变量 var_44 为 0。var_44 作为循环计数器,用来控制循环执行的次数。
mov eax, [ebp+var_20]将局部变量 var_20 的值加载到 eax 寄存器中。var_20 可能是一个用于累加操作的变量。
add eax, [ebp+var_44]将 var_44(计数器)的值加到 eax 中,累加操作。
mov [ebp+var_20], eax将累加后的结果存回 var_20,更新累加器。
mov eax, [ebp+var_44]将计数器 var_44 的值加载到 eax 寄存器中。
add     eax, 1
mov     [ebp+var_44], eax
cmp     [ebp+var_44], 5后面三条指令则是局部变量var_44(循环计数器)自增。
cmp [ebp+var_44], 5比较 var_44 和 5,判断计数器是否小于 5。
jl short loc_411898如果 var_44 小于 5,则跳转回 loc_411898,继续循环。这是一个 "jump if less" 指令,意味着只要 var_44 小于 5,循环继续。
总结
这段代码实现了一个 do-while 循环,其中 var_44 作为循环计数器,从 0 开始,每次循环中都会将计数器的值累加到 var_20,直到计数器达到 5 后,循环结束。
伪代码表示如下:
int var_44 = 0;
do {
    var_20 += var_44;  // 累加操作
    var_44++;  // 计数器递增
} while (var_44 < 5);最后一部分代码则是分别打印第一个循环到第三个循环获得的值,并执行system(pause)代码。

分析起来比较简单且并不是本文重点,所以就不再赘述了。
动态分析
动态分析代码与静态分析基本一致,在这我们将特征代码进行标注:
①for循环

②while循环

③do-while循环

动态分析代码与静态分析基本一致,这边就不再过多赘述了。
在逆向分析循环语句的过程中,通过仔细观察循环的初始化、条件判断和循环体的逻辑,我们能够准确识别出不同类型的循环结构,如 for、while 和 do-while。这些循环的识别不仅帮助我们理解程序的控制流,还为进一步的分析和优化提供了线索。无论是通过静态分析还是动态调试,掌握循环的逆向分析方法将大大提高我们对程序行为的洞察力,并为复杂程序的深入解析奠定基础。



















