lyl610abc 发表于 2021-3-12 01:57

逆向基础笔记二十三 汇编 指针(四)

本帖最后由 lyl610abc 于 2021-3-12 16:29 编辑

继续更新个人的学习笔记,
其它笔记传送门
逆向基础笔记一 进制篇
逆向基础笔记二 数据宽度和逻辑运算
逆向基础笔记三 通用寄存器和内存读写
逆向基础笔记四 堆栈篇
逆向基础笔记五 标志寄存器
逆向基础笔记六 汇编跳转和比较指令
逆向基础笔记七 堆栈图(重点)
逆向基础笔记八 反汇编分析C语言
逆向基础笔记九 C语言内联汇编和调用协定
逆向基础笔记十 汇编寻找C程序入口
逆向基础笔记十一 汇编C语言基本类型
逆向基础笔记十二 汇编 全局和局部 变量
逆向基础笔记十三 汇编C语言类型转换
逆向基础笔记十四 汇编嵌套if else
逆向基础笔记十五 汇编比较三种循环
逆向基础笔记十六 汇编一维数组
逆向基础笔记十七 汇编二维数组 位移 乘法
逆向基础笔记十八 汇编 结构体和内存对齐
逆向基础笔记十九 汇编switch比较if else
逆向基础笔记二十 汇编 指针(一)
逆向基础笔记二十一 汇编 指针(二)
逆向基础笔记二十二 汇编 指针(三)
逆向基础笔记二十四 汇编 指针(五) 系列完结
# 指针四

## 指针数组

### 什么是指针数组

首先回顾一下先前关于数组的知识:

所谓数组就是用于存储**相同数据类型**的集合

再结合先前关于指针的知识:指针的本质也是一种**数据类型**

**于是当数组中存储的成员的数据类型为指针时,该数组就可以称为指针数组(本质是数组)**

------

### 代码

```c
#include "stdafx.h"
void function(){
      int** arr={(int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
}
int main(int argc, char* argv[])
{
      function();
      return 0;
}
```

------

### 反汇编代码

```assembly
9:      int** arr={(int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
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
```

------

### 小总结

- 可以看到指针数组其实并没有什么特别之处,只不过**存储的数组成员的数据类型为指针**而已
- 指针数组的赋值也和先前对指针的赋值**没有什么区别**

------

## 结构体指针

### 什么是结构体指针

所谓结构体指针就是在结构体后加上若干个\*使其称为一个指针类型

### 代码

```c
#include "stdafx.h"
#include <typeinfo>


struct S1{
      int a;
};

void function(){
      S1* s1=(S1*)0x12345678;
    printf("%x\n",s1);
}
int main(int argc, char* argv[])
{
      function();
      return 0;
}
```

#### 运行结果

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311150637448.png)

------

#### 结果分析

可以看到,这里关于结构体指针的使用貌似和普通的指针没有什么区别,但此时会发现这里还没有操作结构体内部的成员

所以结构体指针的实际使用也并不是这样,下面看一个错误的例子

------

### 错误代码

```c
void function(){
      S1* s1=(S1*)0x12345678;
    int a=s1->a;
}
```

只是在上面代码的基础上添加了一个读取结构体成员的语句,查看运行结果

------

#### 运行结果

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311150039289.png)

运行结果不出所料出错了,开始分析错误的原因

------

#### 反汇编代码

```assembly
14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr ,12345678h
15:       int a=s1->a;
0040103F   mov         eax,dword ptr
00401042   mov         ecx,dword ptr
00401044   mov         dword ptr ,ecx
```

------

#### 反汇编分析

**0.执行前s1和s1->a的状态**

s1:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311152832764.png)

------

s1->a:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311152903468.png)

------

**1.为结构体指针s1赋值**

```assembly
14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr ,12345678h
```

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311152423580.png)

------

此时再看看s1->a:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311152552476.png)

可以发现对s1的赋值操作,改变的不是s1->a的值,而是改变了s1->a的地址

其实从执行前s1和s1->a的状态就可以看出,s1存储的内容并不是直接存储结构体成员的内容,而是**存储指向结构体成员的地址**

所以这里对于先前对于s1的赋值操作改变的只是成员的地址,而没有改变成员的值

并且**刚开始时,结构体成员并没有被分配对应的内存地址**

------

**2.访问s1->a**

```assembly
15:       int a=s1->a;
0040103F   mov         eax,dword ptr
00401042   mov         ecx,dword ptr
00401044   mov         dword ptr ,ecx
```

此时出错的原因已经显而易见了,先前对s1的赋值操作修改了s1->a的地址,**使其指向了一个不可访问的地址而导致出错**

------

### 正确代码

前面已经知道了出错的原因是访问了不可访问的地址导致出错,并且刚开始结构体成员没有被分配对应的内存地址

于是只要手动**为结构体成员分配内存地址**即可,这里将使用到malloc函数来进行分配内存地址

------

#### malloc函数

```c
void *malloc(size_t size)
```

参数:size,内存块的大小,以字节为单位

返回值:返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL

相关头文件:malloc.h、alloc.h、stdlib.h

------

大致了解了malloc函数,现在来看代码:

```c
#include "stdafx.h"
#include <malloc.h>                //这里使用了malloc.h
struct S1{
    int a;
      int b;
      int c;
};
void function(){
      S1* s1=(S1*) malloc(sizeof(S1));      //申请一块空间大小正好为S1大小的内存
      s1->a=610;
      s1->b=666;
      s1->c=52;
      printf("%d\n",s1->a);
      printf("%d\n",s1->b);
      printf("%d\n",s1->c);
}
int main(int argc, char* argv[])
{
      function();
      return 0;
}
```

------

#### 运行结果

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311161113903.png)

可以看到结构体的成员能够正常地被改写和访问

------

#### 反汇编代码

```assembly
15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push      0Ch
0040D77A   call      malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr ,eax
16:       s1->a=610;
0040D785   mov         eax,dword ptr
0040D788   mov         dword ptr ,262h
17:       s1->b=666;
0040D78E   mov         ecx,dword ptr
0040D791   mov         dword ptr ,29Ah
18:       s1->c=52;
0040D798   mov         edx,dword ptr
0040D79B   mov         dword ptr ,34h
```

#### 反汇编分析

**1.先看这个malloc函数**

```assembly
15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push      0Ch
0040D77A   call      malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr ,eax
```

1. 压入了参数0C,对应十进制为12,也就是S1的大小
2. 调用malloc函数
3. 堆栈外平衡
4. 将返回值eax赋值给S1

看看返回值eax的内容:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311161655990.png)

可以看到eax就对应了结构体中的成员

eax=结构体成员首地址,里面的结构体成员连续存储

------

**2.赋值,将610对应十六进制262赋值给,对应前面的003807B8**

```assembly
16:       s1->a=610;
0040D785   mov         eax,dword ptr
0040D788   mov         dword ptr ,262h
```

执行后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311161920272.png)

------

**3.赋值,将666对应十六进制29A赋值给,对应前面的003807BC**

```assembly
17:       s1->b=666;
0040D78E   mov         ecx,dword ptr
0040D791   mov         dword ptr ,29Ah
```

执行后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311162143537.png)

------

**4.赋值,将52对应十六进制34赋值给,对应前面的003807C0**

```assembly
18:       s1->c=52;
0040D798   mov         edx,dword ptr
0040D79B   mov         dword ptr ,34h
```

执行后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210311162242585.png)

------

### 小总结

- 结构体指针和普通的指针实际上**并没有什么不同**
- 在对结构体成员进行操作时,需要先对其进行**初始化**(为每个结构体成员分配内存地址)
- 结构体指针并**不直接存储结构体成员**,而是**存储了指向结构体成员的地址**,该地址里存放着所有结构体成员

------

## 数组指针

前面学了指针数组,现在又来个数组指针,中间用结构体指针作了过渡,避免混淆

### 什么是数组指针

所谓数组指针,就是**指向数组的指针(本质是指针)**

既然是指针自然满足先前指针的一切特征:指针的赋值、指针的数据宽度、指针的加减、指针类型相减、指针之间比较

这里就不再赘述先前的内容,有需要可回顾:[逆向基础笔记二十 汇编 指针(一)](https://www.52pojie.cn/thread-1387007-1-1.html)

------

### 数组指针的声明

```c
int (*px);
```

声明如上,数组指针变量为px,类型为:int(*);该数组指针指向的数组为int

------

### 数组指针和指向数组的指针区别

#### 代码

```c
#include "stdafx.h"

void function(){
      int arr={1,2,3,4,5,6};
      //声明一个数组指针,该指针指向数组为:int
      int (*px);      
      //给数组指针赋值,使该数组指针指向arr数组的首地址
      px=(int (*)) &arr;
      //用一个临时变量parr2 存储数组指针
      int (*parr2)=px;
    //*px为数组的首地址,也就是arr,这里就相当于int* arr2=arr;此时的arr2就是指向数组的指针
      int* arr2=*px;
    //初始化变量,准备循环
    int i;
    //循环遍历数组
      for(i=0;i<6;i++){
                printf("%x\t%d\n",arr2+i,arr2);
      }
      printf("\n");      
      int a=(int) (parr2+1);
      int b=(int) (arr2+1);
      printf("%x\t%x\n",a,b);   
}
int main(int argc, char* argv[])
{
      function();
      return 0;
}
```

------

#### 运行结果

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312012226215.png)

首先可以看到数组的正常遍历

然后分别输出了parr2+1和arr2+1的结果,注意这里的结果不同

------

#### 反汇编代码

```assembly
8:      int arr={1,2,3,4,5,6};
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
9:
10:       int (*px);
11:
12:       px=(int (*)) &arr;
00401062   lea         eax,
00401065   mov         dword ptr ,eax
13:
14:       int (*parr2)=px;
00401068   mov         ecx,dword ptr
0040106B   mov         dword ptr ,ecx
15:       int* arr2=*px;
0040106E   mov         edx,dword ptr
00401071   mov         dword ptr ,edx
16:
17:       int i;
18:
19:       for(i=0;i<6;i++){
00401074   mov         dword ptr ,0
0040107B   jmp         function+66h (00401086)
0040107D   mov         eax,dword ptr
00401080   add         eax,1
00401083   mov         dword ptr ,eax
00401086   cmp         dword ptr ,6
0040108A   jge         function+8Fh (004010af)
20:         printf("%x\t%d\n",arr2+i,arr2);
0040108C   mov         ecx,dword ptr
0040108F   mov         edx,dword ptr
00401092   mov         eax,dword ptr
00401095   push      eax
00401096   mov         ecx,dword ptr
00401099   mov         edx,dword ptr
0040109C   lea         eax,
0040109F   push      eax
004010A0   push      offset string "%x\t%d\n" (00422024)
004010A5   call      printf (00401160)
004010AA   add         esp,0Ch
21:       }
004010AD   jmp         function+5Dh (0040107d)
22:       printf("\n");
004010AF   push      offset string "\n" (00422020)
004010B4   call      printf (00401160)
004010B9   add         esp,4
23:
24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr
004010BF   add         ecx,8
004010C2   mov         dword ptr ,ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr
004010C8   add         edx,4
004010CB   mov         dword ptr ,edx
26:       printf("%x\t%x\n",a,b);
004010CE   mov         eax,dword ptr
004010D1   push      eax
004010D2   mov         ecx,dword ptr
004010D5   push      ecx
004010D6   push      offset string "%x\t%x\n" (00422fa4)
004010DB   call      printf (00401160)
004010E0   add         esp,0Ch
```

------

#### 反汇编分析

**1.数组的初始化**

```assembly
8:      int arr={1,2,3,4,5,6};
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
```

数组初始化后对应地址和内容为:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312000021662.png)

------

**2.数组指针的赋值**

```assembly
12:       px=(int (*)) &arr;
00401062   lea         eax,
00401065   mov         dword ptr ,eax
```

直接将arr数组的首地址也就是0012FF14传给了eax

然后再将eax赋值给数值指针px

数值指针赋值后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312012031446.png)

可以看到数值指针里存储的内容为0012FF14即arr的地址

------

**3.将数值指针px赋值给另一个数组指针parr2**

```assembly
14:       int (*parr2)=px;
00401068   mov         ecx,dword ptr
0040106B   mov         dword ptr ,ecx
```

赋值后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312011934479.png)

可以看到此时parr2里存储的内容=px里存储的内容=0012FF14=arr首地址

------

**4.将数组的首地址赋值给arr2,即arr2=arr**

```assembly
14:       int* arr2=*px;
0040D82E   mov         edx,dword ptr
0040D831   mov         dword ptr ,edx
```

------

这里要注意到这里和前面一样都是赋值了

也就是明明赋值的是\*px,但是赋值却和px一样

也可以得出结论\*px=px,那么**为什么*px和px是一样的**?

首先要明确无论是px还是\*px 它们**都是指针**,一个为数组指针,而另一个则为普通指针

它们所指向的地址相同,都指向了arr的首地址0012FF14

区别px和\*px的本质就在于其数据类型是两种**不同的指针结构**

指针在相加减时,加减的基本单位是指针去掉一个\*后的数据宽度(有疑惑可移步[逆向基础笔记二十 汇编 指针(一)](https://www.52pojie.cn/thread-1387007-1-1.html))

- px的数据类型为:int (\*),去掉一个\*后变为int ,数据宽度为int的数据宽度×数组的成员数=4\*2=8
- \*px的数据类型为:int\*,去掉一个\*后变为int,数据宽度=4

------

赋值后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312012117742.png)

------

**5.循环遍历数组**

就是普通的指针循环数组,在先前的笔记中已经有详细介绍,这里不再赘述

------

**6.第二种循环**

```assembly
24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr
004010BF   add         ecx,8
004010C2   mov         dword ptr ,ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr
004010C8   add         edx,4
004010CB   mov         dword ptr ,edx
```

------

通过前面可以得知\*px=px,parr2=arr2,所以这里的=的:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312012608527.png)

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312012624438.png)

------

这里的不同之处就在于一个add了8,另一个add了4,和先前所分析的指针加减的单位相符合,于是产生了不同的结果

------

#### 小总结

- 在一个数组指针前加上\*获得的就是指向数组的指针,如上例中的int* arr2=*px;
- 数值指针和指向数组的指针中存储的内容都是数组的首地址,如上例中的px=\*px=arr=0012FF14
- 数组指针和指向数组的指针的主要区别在进行运算时的单位不同,前者为数据类型宽度×数组成员数,后者为数据类型宽度

------

### 数组指针的应用

可以利用数组指针进行加减时的单位不同来遍历数组的固定间隔的成员

下例为从数组的第二个成员开始,取出间隔为3的数组成员

#### 代码

```c
#include "stdafx.h"
void function(){
      int arr={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
      
      int (*px);      
      //从数组的第二个成员开始
      px=(int (*)) &arr;
               
      int i=0;
      for(i=0;i<15/3;i++){
                printf("%x\t%d\n",px+i,**(px+i));
      //注意这里取了两次*,第一次获得的是指向数组成员的指针,第二个获得的才是数组成员
      }
}
int main(int argc, char* argv[])
{
      function();
      return 0;
}
```

------

#### 运行结果

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210312014359952.png)

------

# 总结

|      | 指针数组                                           | 结构体指针                                                   | 数组指针                                                   |
| ---- | -------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 本质 | 数组                                             | 指针                                                         | 指针                                                         |
| 描述 | 数组成员类型为指针的数组                           | 指向结构体的指针                                             | 指向数组的指针                                             |
| 声明 | type\* arr                                 | struct *structName                                           | int (*name)                                          |
| 补充 | 指针数组的赋值也和先前对指针的赋值**没有什么区别** | 在对结构体成员进行操作时,需要先对其进行**初始化**;结构体指针并**不直接存储结构体成员**,而是**存储了指向结构体成员的地址**,该地址里存放着所有结构体成员 | 数组指针和指向数组的指针的主要区别在进行运算时的单位不同,前者为数据类型宽度×数组成员数,后者为数据类型宽度 |

lyl610abc 发表于 2021-3-12 13:35

本帖最后由 lyl610abc 于 2021-3-12 13:39 编辑

bester 发表于 2021-3-12 09:11
错误代码这里S1* s1=(S1*)x12345678应该是0x12345678

然后像为结构体指针赋值初始化我还有很多问题

关于内存申请
对于malloc和new实际上只是在内部分配内存
真正的申请内存要使用virtualalloc或createfilemapping,这个可以算作是WIN32的内容
有关内存的申请涉及的知识点有内存的段页机制等,属于保护模式的内容,后续笔记会提到
为什么要为结构体申请内存?
对于一个结构体指针,该指针指向结构体的首地址,但由于只声明了一个结构体指针,所以也只为结构体指针分配了内存地址,而没有为结构体分配内存地址,对应前面:执行前s1和s1->a的状态,没有初始化时默认为CCCCCCCC,因此要为结构体分配内存空间,并让结构体指针指向分配的结构体首地址
关于初始化如memset等
前面为结构体申请完空间后,会发现申请完后结构体里存储的数据默认为CDCDCDCD,所以可以使用memset等将数据设置为我们所希望的数值
关于内存相关的知识,如线性地址、物理地址、PDE、PTE等相关内容后续应该会更新,到后面会考虑出一个总结内存申请分配的笔记的{:301_990:}

bester 发表于 2021-3-12 09:11

错误代码这里S1* s1=(S1*)x12345678应该是0x12345678

然后像为结构体指针赋值初始化我还有很多问题
1.给结构体申请内存的函数大概有哪几种,我所知道的是就是malloc 和newnew应该是C++的 malloc 应该是C的,然后 申请内存以后 其实你应该有一个小问题,没有对其进行初始化,初始化的函数我知道有memset 这个应该也是C的 C++有zeromemory fillmemory,所以还有没有别的初始化的函数呢?然后具体什么情况用什么,希望得到解答

2.还有包括很多申请内存函数,我很多时候不知道该用什么函数,比如heapalloc,localalloc,GlobalAlloc,virtualalloc,这个也希望得到解答

最后其实我觉得还有一点没有讲的很明白的地方,就是为什么要给结构体指针申请内存,引用百度的几句话吧,这个也是昨天以前不明白的我理解到的
声明一个结构体变量,无论是否初始化,都开辟内存,声明一个结构体指针变量,对其初始化的时候才会开辟内存。

A a;a是A型的,有3个,当然分配A乘3大小的空间

A* a;    a是A*型的,当然只分配A*大小的空间,而不会分配A大小的空间A*大小只有4字节,而结构体A不一定等于4字节,所以需要用函数分配结构体A大小的空间

附链接

定义结构体与分配内存
C\C++中结构体变量与结构体指针内存分配问题

wylksy 发表于 2021-3-12 07:39

辛苦了楼主

蚂蚁打老虎 发表于 2021-3-12 08:02

谢谢分享

yber 发表于 2021-3-12 08:12

谢谢分享

khadwf 发表于 2021-3-12 08:22

感谢楼主分享。

yyyt110 发表于 2021-3-12 08:27

先标记,再回看

love514415 发表于 2021-3-12 09:06

大佬又更新了{:1_921:}

lifz888 发表于 2021-3-12 09:07

非常好的学习资料集合,支持分享

codetimer 发表于 2021-3-12 09:13

坚持不懈 打卡打卡!{:1_921:}
页: [1] 2 3 4
查看完整版本: 逆向基础笔记二十三 汇编 指针(四)