Windows逆向安全(一)之基础知识(十七)

指针四

指针数组

什么是指针数组

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

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

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

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

代码

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

反汇编代码

9:        int** arr[5]={
    
    (int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
00401038   mov         dword ptr [ebp-14h],1
0040103F   mov         dword ptr [ebp-10h],2
00401046   mov         dword ptr [ebp-0Ch],3
0040104D   mov         dword ptr [ebp-8],4
00401054   mov         dword ptr [ebp-4],5

小总结

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

结构体指针

什么是结构体指针

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

代码

#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;
}

运行结果

在这里插入图片描述
结果分析

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

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

错误代码

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

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

运行结果

扫描二维码关注公众号,回复: 14954469 查看本文章

在这里插入图片描述

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

反汇编代码

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

反汇编分析

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

s1:

在这里插入图片描述
s1->a:

在这里插入图片描述
1.为结构体指针s1赋值

14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr [ebp-4],12345678h

在这里插入图片描述
此时再看看s1->a:

在这里插入图片描述
可以发现对s1的赋值操作,改变的不是s1->a的值,而是改变了s1->a的地址

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

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

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

2.访问s1->a

15:       int a=s1->a;
0040103F   mov         eax,dword ptr [ebp-4]
00401042   mov         ecx,dword ptr [eax]
00401044   mov         dword ptr [ebp-8],ecx

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

正确代码

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

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

malloc函数

void *malloc(size_t size)

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

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

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

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

#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;
}

运行结果

在这里插入图片描述

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

反汇编代码

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

反汇编分析

1.先看这个malloc函数

15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push        0Ch
0040D77A   call        malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr [ebp-4],eax
  1. 压入了参数0C,对应十进制为12,也就是S1的大小
  2. 调用malloc函数
  3. 堆栈外平衡
  4. 将返回值eax赋值给S1

看看返回值eax的内容:

在这里插入图片描述

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

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

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

16:       s1->a=610;
0040D785   mov         eax,dword ptr [ebp-4]
0040D788   mov         dword ptr [eax],262h

执行后:

在这里插入图片描述
3.赋值,将666对应十六进制29A赋值给[ecx+4],对应前面的003807BC

17:       s1->b=666;
0040D78E   mov         ecx,dword ptr [ebp-4]
0040D791   mov         dword ptr [ecx+4],29Ah

执行后:

在这里插入图片描述
4.赋值,将52对应十六进制34赋值给[edx+4],对应前面的003807C0

18:       s1->c=52;
0040D798   mov         edx,dword ptr [ebp-4]
0040D79B   mov         dword ptr [edx+8],34h

执行后:

在这里插入图片描述

小总结

  • 结构体指针和普通的指针实际上并没有什么不同

  • 在对结构体成员进行操作时,需要先对其进行初始化(为每个结构体成员分配内存地址)

  • 结构体指针并不直接存储结构体成员,而是存储了指向结构体成员的地址,该地址里存放着所有结构体成员

数组指针

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

什么是数组指针

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

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

数组指针的声明

int (*px)[2];

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

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

代码

#include "stdafx.h"

void function(){
    
    
        int arr[6]={
    
    1,2,3,4,5,6};
        //声明一个数组指针,该指针指向数组为:int[2]
        int (*px)[2];        
        //给数组指针赋值,使该数组指针指向arr数组的首地址
        px=(int (*)[2]) &arr[0];
        //用一个临时变量parr2 存储数组指针
        int (*parr2)[2]=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[i]);
        }
        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;
}

运行结果

在这里插入图片描述
首先可以看到数组的正常遍历

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

反汇编代码

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

反汇编分析
1.数组的初始化

8:        int arr[6]={
    
    1,2,3,4,5,6};
00401038   mov         dword ptr [ebp-18h],1
0040103F   mov         dword ptr [ebp-14h],2
00401046   mov         dword ptr [ebp-10h],3
0040104D   mov         dword ptr [ebp-0Ch],4
00401054   mov         dword ptr [ebp-8],5
0040105B   mov         dword ptr [ebp-4],6

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

在这里插入图片描述
2.数组指针的赋值

12:       px=(int (*)[2]) &arr[0];
00401062   lea         eax,[ebp-18h]
00401065   mov         dword ptr [ebp-1Ch],eax

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

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

数值指针赋值后:

在这里插入图片描述
可以看到数值指针里存储的内容为0012FF14即arr的地址

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

14:       int (*parr2)[2]=px;
00401068   mov         ecx,dword ptr [ebp-1Ch]
0040106B   mov         dword ptr [ebp-20h],ecx

赋值后:

在这里插入图片描述
可以看到此时parr2里存储的内容=px里存储的内容=0012FF14=arr首地址

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

14:       int* arr2=*px;
0040D82E   mov         edx,dword ptr [ebp-1Ch]
0040D831   mov         dword ptr [ebp-24h],edx

这里要注意到这里和前面一样都是赋值了[ebp-1Ch]

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

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

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

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

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

指针在相加减时,加减的基本单位是指针去掉一个*后的数据宽度

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

赋值后:
在这里插入图片描述
5.循环遍历数组

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

6.第二种循环

24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr [ebp-20h]
004010BF   add         ecx,8
004010C2   mov         dword ptr [ebp-2Ch],ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr [ebp-24h]
004010C8   add         edx,4
004010CB   mov         dword ptr [ebp-30h],edx

通过前面可以得知*px=px,parr2=arr2,所以这里的[ebp-20h]=[ebp-24h]的:

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

小总结

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

数组指针的应用

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

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

代码

#include "stdafx.h"
void function(){
    
    
        int arr[15]={
    
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

        int (*px)[3];        
        //从数组的第二个成员开始
        px=(int (*)[3]) &arr[1];

        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;
}

运行结果

在这里插入图片描述

总结

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_64973687/article/details/130342461