C/C++逆向:循环语句逆向分析

在逆向分析中,循环语句通常会以特定的汇编模式或结构体现出来。常见的循环语句包括 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 循环的特点是循环体在条件检查之前执行,因此逆向时首先识别无条件执行的代码块,条件检查位于循环体之后,查看 CMPTEST 等操作判断是否跳回;循环体通常使用跳转指令(如 JL, JG)回到循环体开头。

逆向分析示例

下面是一个包含 for 循环、while 循环 和 do-while 循环的 C 代码示例,我们可以将它编译成可执行文件,使用IDAx64dbg进行逆向分析,观察它们对应的汇编代码差异。

#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_8var_14var_20var_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的值存入寄存器eaxadd 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_411877

mov [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_411898

mov [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循环

动态分析代码与静态分析基本一致,这边就不再过多赘述了。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值