C/C++逆向:二维数组分析

admin 2024年10月31日18:39:07评论3 views字数 3503阅读11分40秒阅读模式

多维数组是指包含两个或两个以上维度的数组。常见的多维数组有二维数组、三维数组等。对于程序的逆向工程,多维数组的分析通常需要理解数组的内存布局、存储方式以及程序对数组的访问方式。

多维数组的存储方式

多维数组在内存中可以采用按行优先(Row-major order)或按列优先(Column-major order)来存储:

按行优先(Row-major order):数组的元素是按行连续存储的。大多数编程语言如 C/C++ 使用按行优先的方式。
按列优先(Column-major order):数组的元素是按列连续存储的。这种存储方式常见于 Fortran 语言。

在逆向工程的分析中当涉及多维数组的程序时,需要留意以下几个方面:

①内存布局:了解数组的内存布局有助于正确识别数组的起始地址、偏移量和元素访问方式。通常在逆向过程中,通过观察汇编代码对内存的访问模式,可以推断出数组的类型、大小和维度。②数组的下标计算:多维数组的下标计算公式通常遵循 arr[i][j] 对应的地址为:

按行优先:arr + i * 列数 + j
按列优先:arr + j * 行数 + i

假设有一个 C 语言二维数组定义如下:

int arr[3][4] = {
  {1, 2, 3, 4},
  {5, 6, 7, 8},
  {9, 10, 11, 12}
};

在汇编代码中,如果我们看到类似以下的代码片段:

mov eax, [ebp-20h]  ; 加载基地址(arr)到 eax
mov ecx, 2          ; 第三行
mov edx, 3          ; 第四列
mov ebx, 4          ; 每行 4 个元素
imul ecx, ebx       ; ecx = ecx * 4
add ecx, edx        ; ecx += 列数
shl ecx, 2          ; 元素大小是 4 字节,所以偏移量乘以 4
mov eax, [eax + ecx] ; 访问 arr[2][3] 的元素

从这个汇编代码中可以推断出这是一个二维数组访问的模式。通过观察计算偏移量的操作(如 imuladdshl),可以确认数组的大小和内存布局。

接着我们就通过一个简单的二维数组的 C 代码示例,包含对二维数组的定义和访问操作。根据这个例子进行分析。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

    int row = 2;
    int col = 3;
    int value = arr[row][col];  // 访问 arr[2][3],即第 3 行第 4 列的元素
    printf("Value at arr[%d][%d] = %dn", row, col, value);
    system("pause");
    return 0;
}

使用VS生成exe文件后载入IDA中进行静态分析,观察特征。

C/C++逆向:二维数组分析

载入完成后我们快速定位到main()函数主体位置,查看相关的反汇编代码;首先就是二维数组的初始化:

C/C++逆向:二维数组分析

mov     [ebp+var_34], 1
mov     [ebp+var_30], 2
mov     [ebp+var_2C], 3
mov     [ebp+var_28], 4
mov     [ebp+var_24], 5
mov     [ebp+var_20], 6
mov     [ebp+var_1C], 7
mov     [ebp+var_18], 8
mov     [ebp+var_14], 9
mov     [ebp+var_10], 0Ah
mov     [ebp+var_C], 0Bh
mov     [ebp+var_8], 0Ch

代码中使用了一系列 mov 指令,将 12 个值存储在栈上,这表明这些值在内存中是连续存储的,从汇编指令来看,这些值都被存放在 ebp 基址下的不同偏移位置上,并且每个值的偏移量都相差 4 字节。这与 C 语言中数组的连续存储模型相符(尤其是 int 类型的元素在 x86 平台通常占 4 字节),所以基本上但看这串代码基本上可以断定这是一个整型数组。那么在IDA中我们就可以双击偏移量,来到main函数的栈上,选中第一个偏移量,输入*:

C/C++逆向:二维数组分析

输入数组的大小:12,对该数组进行标记

C/C++逆向:二维数组分析

标记完后可以很直观的看出这就是一个数组结构。

C/C++逆向:二维数组分析

二维数组的寻址

接着我们从反汇编代码的角度去查看现二维数组是如何进行寻址的;在这个例子中我们将要取出二维数组中的第 3 行第 4 列的元素即arr[2][3]进行打印,相关代码如下:

int row = 2;
int col = 3;
int value = arr[row][col];  // 访问 arr[2][3],即第 3 行第 4 列的元素
printf("Value at arr[%d][%d] = %dn", row, col, value);

此时,这个操作的反汇编代码如下:

C/C++逆向:二维数组分析

mov     [ebp+var_40], 2
mov     [ebp+var_4C], 3
mov     eax, [ebp+var_40]
shl     eax, 4
lea     ecx, [ebp+eax+var_34]
mov     edx, [ebp+var_4C]
mov     eax, [ecx+edx*4]
mov     [ebp+var_58], eax
mov     eax, [ebp+var_58]
push    eax
mov     ecx, [ebp+var_4C]
push    ecx
mov     edx, [ebp+var_40]
push    edx
push    offset Format   ; "Value at arr[%d][%d] = %dn"
call    j__printf

接着我们就逐步分析这个代码,首先将行索引 2 存入 [ebp+var_40],再将列索引 3 存入 [ebp+var_4C](第一行和第二行代码指令);接着计算行的偏移量:

mov     eax, [ebp+var_40] ; 将行索引(2)加载到 eax 中
shl     eax, 4            ; eax 左移 4 位,相当于乘以 16
lea     ecx, [ebp+eax+var_34] ; 计算基地址 + 行偏移,存入 ecx

这部分代码中,shl eax, 4eax 左移 4 位,等效于将 eax 乘以 16。这表示每一行有 4 列,每列占用 4 个字节(元素是 int 类型,大小为 4 字节),所以每一行的大小是 4 * 4 = 16 个字节,此时eax中的值为32。lea ecx, [ebp+eax+var_34] 的目的是计算 arr[row] 的基地址,其中 var_34 是数组的起始地址偏移量,这个时候放到ecx中的值为[ebp+32(20h)+var_34];此时对应的就是数组中第9个元素的值,也就是第三行的第一个元素。

C/C++逆向:二维数组分析

行的偏移量计算完成后,接着就是计算获取数组中的值,相关代码如下:

mov     edx, [ebp+var_4C] ; 将列索引加载到 edx 中
mov     eax, [ecx+edx*4]  ; 计算 arr[row][col] 的地址,并将其值存入 eax
mov     [ebp+var_58], eax ; 将值存储到 [ebp+var_58]

edx 中保存了列索引的值,然后 ecx + edx*4 计算得到二维数组 arr[2][3] 的具体内存地址,ecx中此时存储的值为第三行的首地址也就是行的偏移量,[行的偏移量+edx*4]即可获得arr[2][3]最后的地址。乘以 4 的原因是每个元素占 4 个字节。接着,访问该内存地址并将数组元素的值存入 eax

最后就是准备 printf 调用的参数:

mov     eax, [ebp+var_58] ; 加载数组元素的值
push    eax               ; 将值压入栈(对应于 printf 中的 %d)
mov     ecx, [ebp+var_4C] ; 加载列索引的值
push    ecx               ; 将列索引压入栈(对应于 printf 中的第二个 %d)
mov     edx, [ebp+var_40] ; 加载行索引的值
push    edx               ; 将行索引压入栈(对应于 printf 中的第一个 %d)
push    offset Format     ; 压入格式化字符串的地址,格式化字符串:"Value at arr[%d][%d] = %dn"
call    j__printf         ; 调用 printf 函数

这段代码准备了调用 printf 所需的参数。格式化字符串 Format"Value at arr[%d][%d] = %dn"。通过依次压入行索引、列索引、数组元素的值和格式化字符串的地址,这就实现了对数组元素值的打印。

在这篇文章中,我们详细探讨了如何通过汇编代码来识别和分析二维数组的特征。从数据的内存布局、索引计算,到访问和读取数组元素,在今后的逆向工程中,希望这些思路能够帮助你更高效地应对类似问题,为分析和理解程序奠定坚实的理论依据。

原文始发于微信公众号(风铃Sec):C/C++逆向:二维数组分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月31日18:39:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   C/C++逆向:二维数组分析https://cn-sec.com/archives/3338245.html

发表评论

匿名网友 填写信息