1、课程目标
- 掌握数组访问的汇编特征
- 理解指针操作的反汇编模式
- 学会识别多维数组
- 掌握指针算术的分析方法
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、课后作业
- 基础练习:编写不同类型数组的操作,观察汇编
- 多维数组:分析二维数组访问的反汇编
- 指针算术:识别反汇编中的指针操作
- 实战还原:从复杂寻址模式还原数据结构