C&C++软件逆向技术

6、数组与指针识别分析

1、课程目标

  1. 掌握数组访问的汇编特征
  2. 理解指针操作的反汇编模式
  3. 学会识别多维数组
  4. 掌握指针算术的分析方法

2、名词解释

术语英文说明
基址Base Address数组或结构体的起始地址
偏移量Offset相对于基址的距离
比例因子Scale Factor索引乘以的元素大小
有效地址Effective Address计算得到的实际内存地址

3、使用工具

工具用途
IDA Pro识别数组和结构体
x64dbg观察内存访问模式

4、技术原理

4.1、数组访问公式

元素地址 = 基址 + 索引 * 元素大小

汇编表示:
mov eax, [base + index * scale]
mov eax, [ebx + ecx * 4]      ; int数组
mov al, [ebx + ecx]           ; char数组
mov ax, [ebx + ecx * 2]       ; short数组

4.2、多维数组

二维数组 arr[M][N]:
元素地址 = base + (i * N + j) * 元素大小

5、代码实现

5.1、示例1:一维数组访问

int ArraySum(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum;
}

// 反汇编:
// mov esi, [ebp+8]        ; arr
// mov ecx, [ebp+12]       ; size
// xor eax, eax            ; sum = 0
// xor edx, edx            ; i = 0
// loop:
// cmp edx, ecx
// jge done
// add eax, [esi + edx*4]  ; sum += arr[i]
// inc edx                 ; i++
// jmp loop
// done:
// ret

5.2、示例2:二维数组

int Matrix[3][4];

int GetElement(int i, int j) {
    return Matrix[i][j];
}

// 反汇编:
// mov eax, [ebp+8]        ; i
// imul eax, 16            ; i * 4 * 4 (4个int每行)
// add eax, offset Matrix  ; base + i * row_size
// mov ecx, [ebp+12]       ; j
// mov eax, [eax + ecx*4]  ; + j * 4
// ret

// 或者编译器可能优化为:
// mov eax, [ebp+8]
// shl eax, 4              ; i * 16 (2的幂次用移位)
// mov ecx, [ebp+12]
// mov eax, [Matrix + eax + ecx*4]

5.3、示例3:指针算术

void PointerArithmetic(int* p, int n) {
    int* end = p + n;      // 指针加整数
    while (p < end) {
        *p = 0;
        p++;               // 指针自增
    }
}

// 反汇编:
// mov esi, [ebp+8]        ; p
// mov eax, [ebp+12]       ; n
// lea edi, [esi + eax*4]  ; end = p + n (注意*4)
// loop:
// cmp esi, edi            ; p < end?
// jae done
// mov dword ptr [esi], 0  ; *p = 0
// add esi, 4              ; p++ (加4不是1)
// jmp loop

5.4、示例4:指针和数组的等价

// 这两种写法编译结果相同
int GetByIndex(int arr[], int i) {
    return arr[i];
}

int GetByPointer(int* p, int i) {
    return *(p + i);
}

// 两者都编译为:
// mov eax, [ebp+8]        ; arr/p
// mov ecx, [ebp+12]       ; i
// mov eax, [eax + ecx*4]  ; arr[i] 或 *(p+i)
// ret

5.5、示例5:字符串处理(char数组)

void ToUpper(char* s) {
    while (*s) {
        if (*s >= 'a' && *s <= 'z') {
            *s -= 32;      // 'a' - 'A' = 32
        }
        s++;
    }
}

// 反汇编:
// mov esi, [ebp+8]
// loop:
// movzx eax, byte ptr [esi]  ; *s
// test al, al                ; == 0?
// jz done
// cmp al, 'a'
// jb skip
// cmp al, 'z'
// ja skip
// sub byte ptr [esi], 32     ; *s -= 32
// skip:
// inc esi                    ; s++ (char所以加1)
// jmp loop

5.6、示例6:识别数组类型

/*
从反汇编识别数组元素类型:

1. 看比例因子 (scale):
   - [base + index*1] -> char或BYTE
   - [base + index*2] -> short或WORD
   - [base + index*4] -> int/float或DWORD
   - [base + index*8] -> __int64/double或QWORD

2. 看访问指令:
   - movzx eax, byte ptr [...] -> 无符号char/BYTE
   - movsx eax, byte ptr [...] -> 有符号char
   - movzx eax, word ptr [...] -> 无符号short
   - mov eax, [...] -> int/DWORD

3. 看循环增量:
   - add esi, 1 -> char数组的指针遍历
   - add esi, 4 -> int数组的指针遍历
*/

5.7、示例7:复杂寻址还原

// 反汇编:
// mov eax, [ebx + ecx*4 + 10h]

// 可能的还原:
// 1. 结构体数组: structs[ecx].field
//    其中field偏移为0x10
// 2. 二维数组: arr[row][col]
//    其中表达式较复杂
// 3. 结构体内的数组: obj->arr[ecx]
//    其中arr偏移为0x10

// 需要结合上下文判断

6、课后作业

  1. 基础练习:编写不同类型数组的操作,观察汇编
  2. 多维数组:分析二维数组访问的反汇编
  3. 指针算术:识别反汇编中的指针操作
  4. 实战还原:从复杂寻址模式还原数据结构