数组是相同数据类型的元素的集合,它们的内存中按顺序连续存放在一起。在汇编状态下访问数组一般是通过基址加变址寻址实现的。
0x01 先找一个数组访问的C语言代码
#include<stdio.h>
int main()
{
static int a[3]={0x11,0x22,0x33};
int i,s=0,b[3];
for(i=0;i<3;i++)
{
s=s+a[i];
b[i]=s;
}
for(i=0;i<3;i++)
{
printf("%d\n",b[i]);
}
return 0;
}
0x02 查看对应的反汇编
第一个for循环将a数字的值逐个加上s,并赋值给了b数组;第二个for循环就是将b数组里的元素逐个输出。
接下来通过反汇编对操作数组的过程简要分析:
8: s=s+a[i];
00401047 8B 4D FC mov ecx,dword ptr [ebp-4] //ebp-4这里存的i的值,并放到ecx寄存器里
0040104A 8B 55 F8 mov edx,dword ptr [ebp-8] //ebp-8存的是s的值,并放到edx寄存器里
0040104D 03 14 8D 30 4A 42 00 add edx,dword ptr [ecx*4+424A30h] // s+a[i] ,看下面解释1
00401054 89 55 F8 mov dword ptr [ebp-8],edx //把相加后的值在放到ebp-8处
9: b[i]=s;
00401057 8B 45 FC mov eax,dword ptr [ebp-4] //这里还是相当于i的值赋值到eax寄存器
0040105A 8B 4D F8 mov ecx,dword ptr [ebp-8] //把相加后的值放到ecx寄存器
0040105D 89 4C 85 EC mov dword ptr [ebp+eax*4-14h],ecx //将ecx中的值放到内存地址为ebp+eax*4-14h处,看下面解释2
10: }
解释1:
dword ptr [ecx4+424A30h],这里对a数组的寻址是通过基址加变址实现的,首先我们在内存中看一下424A30h处的值,可以看到404A30h恰好就是数组的首地址,说明当我们对数组初始化时,就会分配一段内存空间出来用于存放数组中的值,那么对整个数组的取值就可以变成[ecx4+424A30h],其中当ecx的值取0,1,2,就可以遍历整个数组,由于我们把i赋值给了ecx寄存器,所以ecx的取值会通过i的值的变化而变化。
解释2:
mov dword ptr [ebp+eax4-14h],这是对b数组赋值,ebp指向0019FF30,ebp-14h是0019FF1C处,也是b数组的首地址,
在for循环中,i=0,也就是当eax为0的时候,此时s的值为0,ebp-8处的值为11,然后通过ecx再把11放到0019FF1C,
第二次循环,i=1,eax也为1,此时s的值为11,ebp-8处的值是22,于是相加赋值给了[ebp+14-14h]处,也就是0019FF20,
第三次循环,i=2,eax也为2,此时s的值是33,ebp-8处的值是33,于是相加赋值给了[ebp+2*4-14h]处,也就是0019FF24.
这里值得注意的是:我们之前定义的数组b[3] , 内存就会自动分配3个字节的内存空间用于存放数组中的元素。
对b数组的赋值就结束了,接下来看一下如何输出b数组
12: {
13: printf("%d\n",b[i]);
0040107B 8B 45 FC mov eax,dword ptr [ebp-4] //eax作为数组的下标
0040107E 8B 4C 85 EC mov ecx,dword ptr [ebp+eax*4-14h] //还是通过基址加变址的方式对数组进行寻址,并把数组中的值通过循环逐个的赋值给ecx寄存器
00401082 51 push ecx //ecx进栈
00401083 68 1C 20 42 00 push offset string "%d\n" (0042201c) //“%的\n”进栈
00401088 E8 43 00 00 00 call printf (004010d0) //调用输出函数
0040108D 83 C4 08 add esp,8 //平衡堆栈
14: }
0x03 接下来写出这个程序的汇编代码:
写这个程序的汇编代码时,我发现其实完全一个for语句就可以完成上面对b数组的赋值和输出,所以在汇编的时候,我只用了一个for语句。
第一版:
这个是第一次写出来的汇编,结果只输出了第一个值,于是我加断点,调试发现,当执行完第一遍输出语句之后,eax的值就变成了3,再回去执行add eax,1时,eax就变成4,cmp eax,3之后,由于大于3就直接跳出循环了,这样的话,肯定只能输出一个值。
可是eax寄存器的值为什么会变呢?
我发现printf语句执行过程中会用到eax,所以改变了eax的值
第二版
另外,我还有一个发现就是在第一版的基础上用ebx计数,那么就会输出三个值,说明ebx可以用作数组的索引,只是后两个数并不正确,那说明我不能把s的值存入寄存器,而是使用绝对地址!
有了这个发现,我在对代码进行了修改现在就可以成功的输出来了!汇编代码如下:
#include<stdio.h>
int main()
{
static int a[3]={0x11,0x22,0x33};
char *str="%d\n";
_asm{
mov dword ptr [ebp-8],0 //s=0
mov ebx,0 //i=0
jmp judge
round: add ebx,1 ebx进行计数
judge: cmp ebx,3
jge end // 大于等于3则跳转到end
mov edx,dword ptr [ebp-8]
add edx,dword ptr [ebx*4+424A30h] //s=s+a[i]
mov dword ptr [ebp-8],edx // 把相加后的值放入[ebp-8]处
mov dword ptr [ebp+ebx*4-14h],edx //b[i]=s
mov ecx,dword ptr[ebp+ebx*4-14h]
push ecx
push str
call printf
add esp,8
jmp round
end:
xor eax,eax //return 0
}
}
运行结果:
0x04 小结
1.在内存中,数组可存在于栈,数据段及动态内存中,其寻址用“基址+偏移量”实现。这种间接寻址一般出现在给一些数组或是结构赋值的情况下.基址可以是常量,也可以是寄存器,为定值。根据偏移量的不同,可以对结构中相应单元赋值。
2.b[]数组放在栈中,这些栈在编译时分配。数组在声明时可以直接计算偏移地址,针对数组成员寻址是采用实际的偏移量完成的。
3.寄存器ebx可以用作数组的索引,非常类似于高级语言中的变量i