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. 实战还原:从复杂寻址模式还原数据结构