逆向基础笔记十七 汇编二维数组 位移 乘法
本帖最后由 lyl610abc 于 2021-3-12 16:31 编辑继续更新个人的学习笔记,
其它笔记传送门
逆向基础笔记一 进制篇
逆向基础笔记二 数据宽度和逻辑运算
逆向基础笔记三 通用寄存器和内存读写
逆向基础笔记四 堆栈篇
逆向基础笔记五 标志寄存器
逆向基础笔记六 汇编跳转和比较指令
逆向基础笔记七 堆栈图(重点)
逆向基础笔记八 反汇编分析C语言
逆向基础笔记九 C语言内联汇编和调用协定
逆向基础笔记十 汇编寻找C程序入口
逆向基础笔记十一 汇编C语言基本类型
逆向基础笔记十二 汇编 全局和局部 变量
逆向基础笔记十三 汇编C语言类型转换
逆向基础笔记十四 汇编嵌套if else
逆向基础笔记十五 汇编比较三种循环
逆向基础笔记十六 汇编一维数组
逆向基础笔记十八 汇编 结构体和内存对齐
逆向基础笔记十九 汇编switch比较if else
逆向基础笔记二十 汇编 指针(一)
逆向基础笔记二十一 汇编 指针(二)
逆向基础笔记二十三 汇编 指针(四)
逆向基础笔记二十四 汇编 指针(五) 系列完结
# 二维数组
## 二维数组初始化
```c
int arr={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
```
------
### 查看反汇编
```assembly
7: int arr={
8: {1,2,3,4},
0040D498 mov dword ptr ,1
0040D49F mov dword ptr ,2
0040D4A6 mov dword ptr ,3
0040D4AD mov dword ptr ,4
9: {5,6,7,8},
0040D4B4 mov dword ptr ,5
0040D4BB mov dword ptr ,6
0040D4C2 mov dword ptr ,7
0040D4C9 mov dword ptr ,8
10: {9,10,11,12}
0040D4D0 mov dword ptr ,9
0040D4D7 mov dword ptr ,0Ah
0040D4DE mov dword ptr ,0Bh
0040D4E5 mov dword ptr ,0Ch
11: };
```
------
可以发现其存储方式和一维数组并没有什么不同,仍然是从**低地址**开始**连续存储**
### 对比一维数组
```c
int arr={1,2,3,4,5,6,7,8,9,10,11,12};
```
------
查看反汇编代码:
```assembly
15: int arr={1,2,3,4,5,6,7,8,9,10,11,12};
00401038 mov dword ptr ,1
0040103F mov dword ptr ,2
00401046 mov dword ptr ,3
0040104D mov dword ptr ,4
00401054 mov dword ptr ,5
0040105B mov dword ptr ,6
00401062 mov dword ptr ,7
00401069 mov dword ptr ,8
00401070 mov dword ptr ,9
00401077 mov dword ptr ,0Ah
0040107E mov dword ptr ,0Bh
00401085 mov dword ptr ,0Ch
16: }
```
可以看到,其分配方式一模一样
------
### 得出结论
无论是一维数组,二维数组或者其它多维数组,其存储方式实质上并没有区别,都是在内存中连续存储,并**没有所谓的行和列的概念**
对于一个二维数组来说,编译器为其分配空间实际上也是按一维数组来进行分配的
```c
int arr 等同于 int arr
```
拿上面的例子而言就是
```c
int arr 等同于 int arr=int arr
```
因此也可以使用下面这种方式初始化二维数组
```c
int arr={1,2,3,4,5,6,7,8,9,10,11,12};
```
------
### 省略成员的二维数组
前面声明的二维数组每个数组成员都有对应的数值,如果省略了二维数组某些数组成员,又会如何?
```c
int arr={
{1,2},
{5,6,7},
{9}
};
```
------
查看反汇编代码:
```assembly
7: int arr={
8: {1,2},
00401038 mov dword ptr ,1
0040103F mov dword ptr ,2
00401046 xor eax,eax
00401048 mov dword ptr ,eax
0040104B mov dword ptr ,eax
9: {5,6,7},
0040104E mov dword ptr ,5
00401055 mov dword ptr ,6
0040105C mov dword ptr ,7
00401063 xor ecx,ecx
00401065 mov dword ptr ,ecx
10: {9}
00401068 mov dword ptr ,9
0040106F xor edx,edx
00401071 mov dword ptr ,edx
00401074 mov dword ptr ,edx
00401077 mov dword ptr ,edx
11: };
```
在反汇编代码中,存储内容一目了然,对于没有填充的数组成员,**缺省(默认)值为0**
也就是说上面的数组等同于
```c
int arr={
{1,2,0,0},
{5,6,7,0},
{9,0,0,0}
};
```
------
同样对于另一种声明方式也支持不填满
```c
int arr={1,2,3,4,5,6,7,8,9,10};
```
查看反汇编代码
```assembly
7: int arr={1,2,3,4,5,6,7,8,9,10};
00401038 mov dword ptr ,1
0040103F mov dword ptr ,2
00401046 mov dword ptr ,3
0040104D mov dword ptr ,4
00401054 mov dword ptr ,5
0040105B mov dword ptr ,6
00401062 mov dword ptr ,7
00401069 mov dword ptr ,8
00401070 mov dword ptr ,9
00401077 mov dword ptr ,0Ah
0040107E xor eax,eax
00401080 mov dword ptr ,eax
00401083 mov dword ptr ,eax
8: }
```
依旧是**缺省(默认)值为0**
### 省略维数的二维数组
前面知道了二维数组支持省略某些数组成员,同样的,二维数组也支持省略维数
```c
int arr[]={1,2,3,4,5,6,7,8,9,10,11,12};
```
省略了维数之后,这里编译器会**自动分组**,这里为4个一组
------
在省略维数的情况下能否省略成员?
答案是可以的
```c
int arr[]={1,2,3,4,5,6,7,8,9,10};
```
此时的编译器依旧是以4个为一组,后面**不够的部分**自动会补0
------
编译器**不支持**省略后面的维数,如:
```c
int arr[]={1,2,3,4,5,6,7,8,9,10};
```
因为最后面的维数是作为组数,进行分组的
------
## 为什么使用二维数组
经过前面对二维数组初始化的了解,发现二维数组实际上和一维数组并没有什么不同,那么为什么要使用二维数组?
因为使用二维数组更为**直观**,方便对数据进行管理
## 二维数组的寻址
了解完二维数组的初始化后,再来看看二维数组如何寻址
```c
int arr={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
int a=arr;
int i=1,j=2;
int b=arr;
int c=arr;
```
------
### 查看反汇编
```assembly
7: int arr={
8: {1,2,3,4},
0040103E mov dword ptr ,1
00401045 mov dword ptr ,2
0040104C mov dword ptr ,3
00401053 mov dword ptr ,4
9: {5,6,7,8},
0040105A mov dword ptr ,5
00401061 mov dword ptr ,6
00401068 mov dword ptr ,7
0040106F mov dword ptr ,8
10: {9,10,11,12}
00401076 mov dword ptr ,9
0040107D mov dword ptr ,0Ah
00401084 mov dword ptr ,0Bh
0040108B mov dword ptr ,0Ch
11: };
12: int a=arr;
00401092 mov eax,dword ptr
00401095 mov dword ptr ,eax
13: int i=1,j=2;
00401098 mov dword ptr ,1
0040109F mov dword ptr ,2
14: int b=arr;
004010A6 mov ecx,dword ptr
004010A9 shl ecx,4
004010AC lea edx,
004010B0 mov eax,dword ptr
004010B3 mov ecx,dword ptr
004010B6 mov dword ptr ,ecx
15: int c=arr;
004010B9 mov edx,dword ptr
004010BC add edx,dword ptr
004010BF shl edx,4
004010C2 lea eax,
004010C6 mov ecx,dword ptr
004010C9 shl ecx,1
004010CB mov edx,dword ptr
004010CE mov dword ptr ,edx
```
------
#### 常数数组下标的寻址
```assembly
12: int a=arr;
00401092 mov eax,dword ptr
00401095 mov dword ptr ,eax
```
可以看到,当指明了数组下标后,编译器就可以**直接找到**对应的数组成员地址
------
#### 变量数组下标的寻址
```assembly
14: int b=arr;
004010A6 mov ecx,dword ptr
004010A9 shl ecx,4
004010AC lea edx,
004010B0 mov eax,dword ptr
004010B3 mov ecx,dword ptr
004010B6 mov dword ptr ,ecx
```
稍微分析一下这段代码
首先将 i 赋给ecx
```assembly
004010A6 mov ecx,dword ptr
```
然后对ecx左移4位,相当于ecx=ecx\*2^4=ecx\*16,关于左移右移的详细说明在后面
```assembly
004010A9 shl ecx,4
```
执行前:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210306162227342.png)
执行后:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210306162256365.png)
------
可以看到原本的ecx从1变成了0x10=16
为什么是乘以16?具体在下面的**总结寻址方式**里说明
------
接着向下看:
```assembly
004010AC lea edx,
```
这里先不管ecx,看看对应什么
```assembly
0040103E mov dword ptr ,1
```
可以发现正好对应数组的一个数组成员
所以这里便是从数组的第一个成员开始,加上ecx的偏移,先找到**目标数组成员所在行数**的第一个成员地址
------
再接着向下看:
```assembly
004010B0 mov eax,dword ptr
```
这里是将 j 的值赋给eax
------
再看:
```assembly
004010B3 mov ecx,dword ptr
```
用前面得到的edx,也就是目标成员数组成员所在行数的第一个成员地址加上偏移:eax*4,即数组下标 × 数据宽度得到目标数组成员
然后将目标数组成员的值赋给ecx
------
最后:
```assembly
004010B6 mov dword ptr ,ecx
```
将ecx,也就是目标数组成员的值赋给 b
------
再下面的变量计算无非就是先算出值再操作,这里就不再赘述了
## 总结寻址方式
二维数组的寻址方式大体可分为两种:
- 常量
- 变量
------
### 常量
通过常量给定下标来寻址时 和 一维数组 一样,编译器可以**直接通过下标**来找到对应的数组成员地址
------
### 变量
相比之下,通过变量给定下标来寻址时则相对麻烦一些
为使得说明不那么抽象就拿前面的数组为例
```c
int arr={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
```
------
首先是拿出数组的行数:3,并将这个数 × 16,为什么是乘以16?
这里的16=4*4,一个4为数组的组数,也就是arr\\[**4\]**中的4
另一个4为数组成员的数据宽度:4(单位为字节),int类型在32位系统中占4字节
------
再举一个例子:
```c
int arr={
{1,2,3,4,0},
{5,6,7,8,0},
{9,10,11,12,0}
};
```
此时再查看对应的反汇编代码:
```assembly
15: int b=arr;
004010B5 mov ecx,dword ptr
004010B8 imul ecx,ecx,14h
```
可以看到原本的shl 4变成了imul ecx,ecx,14h
14h对应的十进制为20=4*5,4为数组成员的数据宽度,5则为arr\\[**5**\]中的5
------
然后和一维数组的寻址有些类似,都是从数组的第一个成员地址开始,加上偏移,只不过二维数组需要二次寻址
1. 第一次寻址找到数组成员所在行数
2. 第二次寻址才真正找到数组成员
第一次寻址就是将通过数组第一个成员地址+ i × j × 数组成员类型的数据宽度 得到的
第二次寻址则是通过第一次寻址结果+ j*数组成员类型的数据宽度得到的
------
### 二维数组变量寻址流程图
将上述的分析画成流程图:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210306181804222.png)
# 位移
前面在寻址的过程中分别用到了乘法,当乘数为2的n次方时,可以直接使用左移来实现,无需imul指令
汇编中有常用的两种位移指令:shl和shr
使用方法并没有太大的区别,这里就拿shl指令作为例子
## shl指令
SHL是一个汇编指令,作用是**逻辑左移**指令,将目的操作数顺序左移1位或CL寄存器中指定的位数。左移一位时,操作数的最高位移入进位标志位CF,最低位补零。
运算例子:
!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/Shl.png)
乘法对应例子:
```c
int i=1;
i=i*4;
i=i*8;
i=i*16;
```
------
```assembly
8: i=i*4;
0040103F mov eax,dword ptr
00401042 shl eax,2
00401045 mov dword ptr ,eax
9: i=i*8;
00401048 mov ecx,dword ptr
0040104B shl ecx,3
0040104E mov dword ptr ,ecx
10: i=i*16;
00401051 mov edx,dword ptr
00401054 shl edx,4
00401057 mov dword ptr ,edx
```
可以看到\*4时,对应左移两位,\*8则对应左移3位,*16对应左移4位
# 乘法
## imul指令
imul指令使用起来和div指令有些类似,有关div指令可以参考:[【原创】另类汇编解反转数字题](https://www.52pojie.cn/thread-1384402-1-1.html#37219640_div%E6%8C%87%E4%BB%A4)
IMUL(有符号数乘法)指令执行有符号整数乘法
x86 指令集支持三种格式的 IMUL 指令:单操作数、双操作数和三操作数。单操作数格式中,乘数和被乘数大小相同,而乘积的大小是它们的两倍
这里限于篇幅,仅介绍上面使用到的三操作数,其余部分可以参考:[汇编语言IMUL指令:有符号数乘法](http://c.biancheng.net/view/3603.html)
### 例子
```c
int i=1;
i=i*5;
i=i*6;
i=i*7;
```
------
### 查看汇编代码
```assembly
7: int i=1;
00401038 mov dword ptr ,1
8: i=i*5;
0040103F mov eax,dword ptr
00401042 imul eax,eax,5
00401045 mov dword ptr ,eax
9: i=i*6;
00401048 mov ecx,dword ptr
0040104B imul ecx,ecx,6
0040104E mov dword ptr ,ecx
10: i=i*7;
00401051 mov edx,dword ptr
00401054 imul edx,edx,7
00401057 mov dword ptr ,edx
```
可以看到:这里使用了三操作数的imul指令,分别乘以了5、6、7
当imul指令为三操作数时,就是将第二个操作数和第三个操作数的乘积保存到第一个操作数中
拿上面的例子来说:
```assembly
00401042 imul eax,eax,5
```
就是(第一个操作数)eax=(第二个操作数)eax × (第三个操作数)5
------ 18077484116 发表于 2021-5-8 20:17
arr 的值是不是还没定义
edx的值怎么变成1了
arr没定义,arr在数组中找不到对应的值
数组访问越界了,edx的值变成1是因为越界访问的地址的值为1
可以通过寄存器窗口和内存窗口观察:
arr 的值是不是还没定义
edx的值怎么变成1了 追随打卡,滴~滴~,滴~~ 好东西!收藏了 感谢楼主的坚持不懈,必须支持。。。。 学习一下~ 好像有点难~ 支持!!! 非常棒的教程感谢大佬 学习一下~ 多谢楼主分享,这些基础还真的掌握好了 谢谢,大佬 又可以学习了