反汇编(Disassembly) 即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。
数组和指针都是针对地址操作,但它们有许多不同之处,数组是相同数据类型的集合,以线性方式连续存储在内存中,而指针只是一个保存地址值的4字节变量。
在使用中,数组名是一个地址常量值,保存数组首元素地址不可修改,只能以此为基地址访问内存数据;而指针却是一个变量,只要修改指针中所保存的地址数据,就可以随意访问,不受约束.本章将深入介绍数组的构成以及两种寻址方式。
定义单循环一维的数组: 数组默认是使用局部变量存储的,拥有局部变量的所有特性,且数组中的数据在内存中是线性存储的,其数据由低到高在内存中的堆栈中存储,如下是一个简单的数组定义:
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[5] = { 1, 2, 3, 4, 5 };
int x;
for (x = 0; x < 5; x++){
printf("打印数组元素: %d \n", array[x]);
}
return 0;
}
第一种Debug版反汇编代码如下,可以看到重点的部分是mov ecx,dword ptr ss:[ebp+eax*4-0x18]
其中eax寄存器存储的就是数组下标,而乘以4是因为整数占4字节的存储空间所以要乘以4,最后的减0x18则是将堆栈指向第一个数组元素.
004113DE | C745 E8 01000000 | mov dword ptr ss:[ebp-0x18],0x1 | 数组第1个元素
004113E5 | C745 EC 02000000 | mov dword ptr ss:[ebp-0x14],0x2 | 数组第2个元素
004113EC | C745 F0 03000000 | mov dword ptr ss:[ebp-0x10],0x3 | 数组第3个元素
004113F3 | C745 F4 04000000 | mov dword ptr ss:[ebp-0xC],0x4 | 数组第4个元素
004113FA | C745 F8 05000000 | mov dword ptr ss:[ebp-0x8],0x5 | 数组第5个元素
00411401 | C745 DC 00000000 | mov dword ptr ss:[ebp-0x24],0x0 | for循环初始化条件
00411408 | EB 09 | jmp 0x411413 |
0041140A | 8B45 DC | mov eax,dword ptr ss:[ebp-0x24] | x++
0041140D | 83C0 01 | add eax,0x1 | for循环每次加1
00411410 | 8945 DC | mov dword ptr ss:[ebp-0x24],eax |
00411413 | 837D DC 05 | cmp dword ptr ss:[ebp-0x24],0x5 |
00411417 | 7D 21 | jge 0x41143A | 判断x是否大于等于5
00411419 | 8BF4 | mov esi,esp | main.c:9
0041141B | 8B45 DC | mov eax,dword ptr ss:[ebp-0x24] | 取出第一个数组元素的基址
0041141E | 8B4C85 E8 | mov ecx,dword ptr ss:[ebp+eax*4-0x18] | 一维数组寻址公式
00411422 | 51 | push ecx |
00411423 | 68 58584100 | push consoleapplication1.415858 | 415858:"打印数组元素: %d \n"
00411428 | FF15 14914100 | call dword ptr ds:[<&printf>] | 打印出来
0041142E | 83C4 08 | add esp,0x8 |
00411431 | 3BF4 | cmp esi,esp |
00411433 | E8 FEFCFFFF | call 0x411136 |
00411438 | EB D0 | jmp 0x41140A | main.c:10
0041143A | 33C0 | xor eax,eax | main.c:11
第二种Release版反汇编代码如下,相比于Debug版本的代码,编译器对代码进行了一定程度的优化,g观察反汇编代码可以看出数组元素1-4是直接通过mxx0寄存器直接存储的,也就是编译器将其写死在了代码里,其他地方变化不大,需要注意在寻址过程中,数组不同于局部变量,不会被赋予常量值而使用常量传播.
00401006 | 66:0F6F05 10214000 | movdqa xmm0,xmmword ptr ds:[<__xmm@000000040000000300 | 将1-4元素压入xmm0寄存器
0040100E | 56 | push esi |
0040100F | 57 | push edi |
00401010 | 8B3D 90204000 | mov edi,dword ptr ds:[<&printf>] | edi 存储printf地址
00401016 | 33F6 | xor esi,esi | 清除esi做循环条件
00401018 | F3:0F7F45 EC | movdqu xmmword ptr ss:[ebp-0x14],xmm0 | 直接将1-4写入内存
0040101D | C745 FC 05000000 | mov dword ptr ss:[ebp-0x4],0x5 | 最后写一个5
00401024 | FF74B5 EC | push dword ptr ss:[ebp+esi*4-0x14] | 寻址方式未变动
00401028 | 68 00214000 | push disable.402100 | 402100:"打印数组元素: %d \n"
0040102D | FFD7 | call edi | 调用Printf
0040102F | 46 | inc esi | 每次递增
00401030 | 83C4 08 | add esp,0x8 |
00401033 | 83FE 05 | cmp esi,0x5 | 判断是否小于5
00401036 | 7C EC | jl 0x401024 |
这里我写了一段双循环代码,当程序运行后外部每循环一次内层则循环3次,你可以尝试逆向它并总结经验.
#include <stdio.h>
int main(int argc, char *argv[])
{
int array1[5] = { 1, 2, 3, 4, 5 };
int array2[3] = { 99,88,77 };
int x,y;
int external_len = sizeof(array1) / sizeof(array1[0]);
for (x = 0; x < external_len; x++)
{
printf("外层循环计数: %d \n", array1[x]);
int inside_len = sizeof(array2) / sizeof(array2[0]);
for (y = 0; y < inside_len; y++)
{
printf("内层循环计数: %d \n", array2[y]);
}
printf("\n");
}
getchar();
return 0;
}
汇编代码
004113CC | 8DBD E0FEFFFF | lea edi,dword ptr ss:[ebp-0x120] |
004113D2 | B9 48000000 | mov ecx,0x48 | 48:'H'
004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |
004113DC | F3:AB | rep stosd |
004113DE | C745 E8 01000000 | mov dword ptr ss:[ebp-0x18],0x1 | 数组第1个元素
004113E5 | C745 EC 02000000 | mov dword ptr ss:[ebp-0x14],0x2 | 数组第2个元素
004113EC | C745 F0 03000000 | mov dword ptr ss:[ebp-0x10],0x3 | 数组第3个元素
004113F3 | C745 F4 04000000 | mov dword ptr ss:[ebp-0xC],0x4 | 数组第4个元素
004113FA | C745 F8 05000000 | mov dword ptr ss:[ebp-0x8],0x5 | 数组第5个元素
00411401 | C745 D4 63000000 | mov dword ptr ss:[ebp-0x2C],0x63 | main.c:6, 63:'c'
00411408 | C745 D8 58000000 | mov dword ptr ss:[ebp-0x28],0x58 | 58:'X'
0041140F | C745 DC 4D000000 | mov dword ptr ss:[ebp-0x24],0x4D | 4D:'M'
00411416 | C745 B0 05000000 | mov dword ptr ss:[ebp-0x50],0x5 | main.c:9
0041141D | C745 C8 00000000 | mov dword ptr ss:[ebp-0x38],0x0 | main.c:10
00411424 | EB 09 | jmp 0x41142F |
00411426 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] |
00411429 | 83C0 01 | add eax,0x1 | 外层循环递增条件
0041142C | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax |
0041142F | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | 外层循环变量
00411432 | 3B45 B0 | cmp eax,dword ptr ss:[ebp-0x50] | 比较外层循环
00411435 | 7D 7D | jge 0x4114B4 |
00411437 | 8BF4 | mov esi,esp | main.c:12
00411439 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | 外层循环,循环次数 0,1,2,3,4
0041143C | 8B4C85 E8 | mov ecx,dword ptr ss:[ebp+eax*4-0x18] | 通过公式定位到元素
00411440 | 51 | push ecx |
00411441 | 68 58584100 | push consoleapplication1.415858 | 415858:"外层循环计数: %d \n"
00411446 | FF15 10914100 | call dword ptr ds:[<&printf>] | 打印外层循环计数
0041144C | 83C4 08 | add esp,0x8 |
0041144F | 3BF4 | cmp esi,esp |
00411451 | E8 E0FCFFFF | call 0x411136 |
00411456 | C745 A4 03000000 | mov dword ptr ss:[ebp-0x5C],0x3 | 指定内层循环计数
0041145D | C745 BC 00000000 | mov dword ptr ss:[ebp-0x44],0x0 | y=0
00411464 | EB 09 | jmp 0x41146F |
00411466 | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] |
00411469 | 83C0 01 | add eax,0x1 | 内层循环y每次递增
0041146C | 8945 BC | mov dword ptr ss:[ebp-0x44],eax |
0041146F | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] |
00411472 | 3B45 A4 | cmp eax,dword ptr ss:[ebp-0x5C] | 比较内层循环是否大于3
00411475 | 7D 21 | jge 0x411498 |
00411477 | 8BF4 | mov esi,esp | main.c:16
00411479 | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] | 取出y的值
0041147C | 8B4C85 D4 | mov ecx,dword ptr ss:[ebp+eax*4-0x2C] | 通过公式定位到元素位置
00411480 | 51 | push ecx |
00411481 | 68 70584100 | push consoleapplication1.415870 | 415870:"内层循环计数: %d \n"
00411486 | FF15 10914100 | call dword ptr ds:[<&printf>] | 打印
0041148C | 83C4 08 | add esp,0x8 |
0041148F | 3BF4 | cmp esi,esp |
00411491 | E8 A0FCFFFF | call 0x411136 |
00411496 | EB CE | jmp 0x411466 | main.c:17
00411498 | 8BF4 | mov esi,esp | main.c:18
0041149A | 68 88584100 | push consoleapplication1.415888 | 415888:L"\n"
0041149F | FF15 10914100 | call dword ptr ds:[<&printf>] |
004114A5 | 83C4 04 | add esp,0x4 |
004114A8 | 3BF4 | cmp esi,esp |
004114AA | E8 87FCFFFF | call 0x411136 |
004114AF | E9 72FFFFFF | jmp 0x411426 | main.c:19
定义并使用二维的数组: 二维数组是一维数组的高阶抽象,其在内存中的排列也是线性存储的,只是在寻址方式上有所区别而已.
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
int *Pointer1 = &array[0][0];
printf("array[0][0]基址: %x \n", Pointer1);
printf("array[0][0]数据:%d\n", *(Pointer1));
printf("arrya[0][1]数据:%d\n", *(Pointer1 + 1));
int *Pointer2 = &array[1][2];
printf("array[1][2]基址: %x \n", Pointer2);
printf("数组元素个数:%d\n", sizeof(array) / sizeof(int));
return 0;
}
一张图理解寻址过程。
首先来研究一下第一个元素数组元素的寻址,也就是寻找到Pointer1 = > array[0][0]
这个内存的空间,此处省略不必要的数据节约空间.
004113DE | C745 E4 01000000 | mov dword ptr ss:[ebp-0x1C],0x1 | 数组第1个元素
004113E5 | C745 E8 02000000 | mov dword ptr ss:[ebp-0x18],0x2 | 数组第2个元素
004113EC | C745 EC 03000000 | mov dword ptr ss:[ebp-0x14],0x3 | 数组第3个元素
004113F3 | C745 F0 04000000 | mov dword ptr ss:[ebp-0x10],0x4 | 数组第4个元素
004113FA | C745 F4 05000000 | mov dword ptr ss:[ebp-0xC],0x5 | 数组第5个元素
00411401 | C745 F8 06000000 | mov dword ptr ss:[ebp-0x8],0x6 | 数组第6个元素
00411408 | B8 0C000000 | mov eax,0xC | 每一个一维数组的大小3*4=0C
0041140D | 6BC8 00 | imul ecx,eax,0x0 | 相乘ecx作为数组维度寻址
00411410 | 8D540D E4 | lea edx,dword ptr ss:[ebp+ecx-0x1C] | 取第一个数组元素首地址(基地址)
00411414 | B8 04000000 | mov eax,0x4 | 每个元素占用4字节空间
00411419 | 6BC8 00 | imul ecx,eax,0x0 | 计算第一个数组元素偏移
0041141C | 03D1 | add edx,ecx | 得出array[0][0]的地址
0041141E | 8955 D8 | mov dword ptr ss:[ebp-0x28],edx | edx存储的就是 array[0][0] 的地址
00411421 | 8BF4 | mov esi,esp | main.c:8
00411423 | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] |
00411426 | 50 | push eax | 压入堆栈,打印出来
00411427 | 68 58584100 | push consoleapplication1.415858 | 415858:"array[0][0]基址: %x \n"
0041142C | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411432 | 83C4 08 | add esp,0x8 |
00411435 | 3BF4 | cmp esi,esp |
00411437 | E8 FAFCFFFF | call 0x411136 |
0041143C | 8BF4 | mov esi,esp | main.c:9
0041143E | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] | 取出数组基地址
00411441 | 8B08 | mov ecx,dword ptr ds:[eax] | 打印出 array[0][0]
00411443 | 51 | push ecx |
00411444 | 68 74584100 | push consoleapplication1.415874 | 415874:"array[0][0]数据:%d\n"
00411449 | FF15 14914100 | call dword ptr ds:[<&printf>] |
0041144F | 83C4 08 | add esp,0x8 |
00411452 | 3BF4 | cmp esi,esp |
00411454 | E8 DDFCFFFF | call 0x411136 |
00411459 | 8BF4 | mov esi,esp | main.c:10
0041145B | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] |
0041145E | 8B48 04 | mov ecx,dword ptr ds:[eax+0x4] | 打印出 array[0][1]
00411461 | 51 | push ecx |
00411462 | 68 90584100 | push consoleapplication1.415890 | 415890:"arrya[0][1]数据:%d\n"
00411467 | FF15 14914100 | call dword ptr ds:[<&printf>] |
0041146D | 83C4 08 | add esp,0x8 |
00411470 | 3BF4 | cmp esi,esp |
00411472 | E8 BFFCFFFF | call 0x411136 |
00411477 | B8 0C000000 | mov eax,0xC | 每一个一维数组的大小3*4=0C
0041147C | C1E0 00 | shl eax,0x0 |
0041147F | 8D4C05 E4 | lea ecx,dword ptr ss:[ebp+eax-0x1C] | 取第一个数组元素首地址(基地址)
00411483 | BA 04000000 | mov edx,0x4 | 每个元素占用4字节空间
00411488 | D1E2 | shl edx,0x1 | 移位前 edx=4 移位后 edx=8
0041148A | 03CA | add ecx,edx | 得出array[1][2]的地址
0041148C | 894D CC | mov dword ptr ss:[ebp-0x34],ecx | 保存这个内存地址
0041148F | 8BF4 | mov esi,esp | main.c:13
00411491 | 8B45 CC | mov eax,dword ptr ss:[ebp-0x34] | 取出内存地址中的值
00411494 | 50 | push eax | 压入堆栈,准备输出
00411495 | 68 AC584100 | push consoleapplication1.4158AC | 4158AC:"array[1][2]基址: %x \n"
0041149A | FF15 14914100 | call dword ptr ds:[<&printf>] |
004114A0 | 83C4 08 | add esp,0x8 |