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

指针

什么是指针

一般关于指针的解释都离不开地址。这里先暂且忘记这个概念

指针其实也是一种数据类型,和先前学习的int float等数据类型没有实质上的区别,只不过这个数据类型是在先前学习的所有数据类型后面加上若干个*号,如char *,int *等等,这种数据类型被称为指针

  • 任意类型后面都可以加上*号,使其成为新的指针数据类型
  • *可以是任意多个

指针的声明

指针的声明其实在前面介绍什么是指针的时候就已经讲到了,例子如下:

struct S1{
        int a;
};

void function(){                
        char* a;
        short** b;
        int*** c;
        long**** d;
        _int64***** e;
        float****** f;
        double******* g;
        S1******** s1;
}

可以看到所有的其它数据类型(包括结构体)后面加上若干个*后就是所谓的指针了

推荐的声明方式如上

但也可以这样将*放在变量前面,但不推荐,因为这样相当于将这个数据类型拆开了,不利于理解

struct S1{
        int a;
};

void function(){                
        char *a;
        short **b;
        int ***c;
        long ****d;
        _int64 *****e;
        float ******f;
        double *******g;
        S1 ********s1;
}

指针的赋值

在说指针的赋值之前先看看先前普通变量的赋值

普通变量的赋值貌似是直接使用 变量=值即可,但其实是编译器简化了赋值的步骤,实际上在赋值前本应该加上要赋值类型

例子如下:

void function(){                
        int a;
    a=610;
    a=(int)610;
}

现在再来看指针的赋值

void function(){                
        char* a;
        a=(char*) 610;
    int** b;
    b=(int**) 610;
}

在要赋的值前面加上指针的类型即可,貌似和普通变量的赋值并无太大差别,此时也注意到这里的指针也和地址没有什么关联

指针的的类型转换这里暂且不提

指针的数据宽度

研究数据宽度方法

先前研究过其它基本变量的数据宽度,会发现char、short、int都是按照4字节来分配的(内存对齐),但实际使用的情况下则是按照其原本类型的数据宽度来赋值或进行其它操作的

如:

void function(){                
        char a;
        short b;
        int c;
        a=1;
        b=2;
        c=3;
}

其对应的反汇编代码为:

11:   void function(){
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,4Ch
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-4Ch]
0040101C   mov         ecx,13h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
12:       char a;
13:       short b;
14:       int c;
15:       a=1;
00401028   mov         byte ptr [ebp-4],1
16:       b=2;
0040102C   mov         word ptr [ebp-8],offset function+20h (00401030)
17:       c=3;
00401032   mov         dword ptr [ebp-0Ch],3
18:   }
00401039   pop         edi
0040103A   pop         esi
0040103B   pop         ebx
0040103C   mov         esp,ebp
0040103E   pop         ebp
0040103F   ret

可以注意到此时提升的堆栈为4Ch,而默认(空函数时)提升的堆栈为40h

00401013   sub         esp,4Ch

于是此时为三个变量分配的空间是:4Ch-40h=0xC=12=3×4,即为char short int都分配了4个字节

但在这三个变量赋值的时候展现出来的就是其原本的数据类型宽度了

15:       a=1;
00401028   mov         byte ptr [ebp-4],1

在char类型的a中的赋值宽度是 byte,1字节

16:       b=2;
0040102C   mov         word ptr [ebp-8],offset function+20h (00401030)

在int类型的c中的赋值宽度是dword,4字节

研究指针数据宽度

于是如法炮制,按照前面的方法来研究指针的数据宽度

在前面的数据类型后添加*,使其成为指针类型

void function(){                
        char* a;
        short* b;
        int* c;
        a=(char*)  1;
        b= (short*) 2;
        c=(int*)  3;
}

其对应的反汇编代码为

11:   void function(){
00401010   push        ebp
00401011   mov         ebp,esp
00401013   sub         esp,4Ch
00401016   push        ebx
00401017   push        esi
00401018   push        edi
00401019   lea         edi,[ebp-4Ch]
0040101C   mov         ecx,13h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos    dword ptr [edi]
12:       char* a;
13:       short* b;
14:       int* c;
15:       a=(char*)  1;
00401028   mov         dword ptr [ebp-4],1
16:       b= (short*) 2;
0040102F   mov         dword ptr [ebp-8],2
17:       c=(int*)  3;
00401036   mov         dword ptr [ebp-0Ch],3
18:   }
0040103D   pop         edi
0040103E   pop         esi
0040103F   pop         ebx
00401040   mov         esp,ebp
00401042   pop         ebp
00401043   ret

直接观察对应的赋值语句:

15:       a=(char*)  1;
00401028   mov         dword ptr [ebp-4],1
16:       b= (short*) 2;
0040102F   mov         dword ptr [ebp-8],2
17:       c=(int*)  3;
00401036   mov         dword ptr [ebp-0Ch],3

可以看到,所有赋值的宽度都为dowrd,说明无论是char*、short、int\其数据宽度都为4字节

可以使用同样的方法研究float、double、struct等其它数据类型

并且在这里会注意到,指针类型的赋值和非指针类型的赋值在反汇编中并没有什么区别

总结

无论是什么类型,在其后面加上(无论加几个\都一样)后其数据宽度都变为4字节

指针的加减

例子

指针类型也支持加减的操作,但不支持乘和除(编译器决定的),来看例子:

#include "stdafx.h"

void function(){                
        char* a;
        short* b;
        int* c;
        a=(char*)  1;
        b= (short*) 2;
        c=(int*)  3;

        a++;
        b++;
        c++;

        printf("a:%d\t b:%d\tc:%d\n",a,b,c);

}

int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

在这里插入图片描述

分析

这里会观察到结果并不是想象中的2,3,4;而是2,4,7

细心的小伙伴肯定发现了:

  • 2 = 1 + 1 (char数据宽度为1字节)
  • 4 = 2 + 2 (short数据宽度为2字节)
  • 7 = 3 + 4 (int数据宽度为4字节)

结果是加上了原本各自的数据类型的宽度

拓展例子

前面只是都是一级指针,现在将指针换为二级指针:

void function(){                
        char** a;
        short** b;
        int** c;
        a=(char**)  1;
        b= (short**) 2;
        c=(int**)  3;

        a++;
        b++;
        c++;

        printf("a:%d\t b:%d\tc:%d\n",a,b,c);

}

运行结果

在这里插入图片描述

分析

此时的结果为:

  • 5= 1 + 4 (char* 数据宽度为4字节)

  • 6= 2 + 4 (short* 数据宽度为4字节)

  • 7 = 3 + 4 (int* 数据宽度为4字节)

结果为加上 去掉一个*后的数据宽度

拓展例子二

前面的加法操作都只增加了1,现在再来查看增加大于1时的情况

void function(){                
        char* a;
        short* b;
        int* c;
        a=(char*)  1;
        b= (short*) 2;
        c=(int*)  3;

        a=a+5;
        b=b+5;
        c=c+5;

        printf("a:%d\t b:%d\tc:%d\n",a,b,c);

}

运行结果

在这里插入图片描述

分析

此时的结果为:

  • 6= 1 + 5*1 (char 数据宽度为1字节)
  • 12= 2 + 5*2 (short 数据宽度为2字节)
  • 23 = 3 + 5*4 (int 数据宽度为4字节)

结果为加上 去掉一个*后的数据宽度 × 增加的数值

总结

无论是指针的加亦或是减(这里只演示了加法,但减法同理),其加或减的单位为去掉一个*后的数据宽度

也就是实际增减的数值=去掉一个*后的数据宽度 × 增减的数值

指针类型相减

前面提到的指针的加减都是同一个指针里的加减,但指针之间其实也支持相减操作(不支持相加)

但指针之间的加减要求指针的类型必须一致,即char类型只能和char类型相加减,不能和char**或其它类型相加减

例子

void function(){                
        char* a;
        char* b;
        short* c;
        short* d;
        int* e;
        int* f;

        a=(char*) 200;
        b=(char*) 100;

        c=(short*) 200;
        d=(short*) 100;

        e=(int*) 200;
        f=(int*) 100;

        printf("%d\n",a-b);
        printf("%d\n",c-d);
        printf("%d\n",e-f);

}

运行结果

在这里插入图片描述

分析

此时的结果为:

  • 100 = (200 - 100)/1(char 数据宽度为1字节)
  • 50 = (200 - 100)/2 (short 数据宽度为2字节)
  • 25 = (200 - 100)/4 (int 数据宽度为4字节)

结果为相减完后再除以 原本各自的数据宽度

扩展例子

前面只是都是一级指针,现在将指针换为四级指针:

void function(){                
        char**** a;
        char**** b;
        short**** c;
        short**** d;
        int**** e;
        int**** f;

        a=(char****) 200;
        b=(char****) 100;

        c=(short****) 200;
        d=(short****) 100;

        e=(int****) 200;
        f=(int****) 100;

        printf("%d\n",a-b);
        printf("%d\n",c-d);
        printf("%d\n",e-f);

}

运行结果

在这里插入图片描述

分析

此时的结果为:

  • 25 = (200 - 100)/4(char*** 数据宽度为4字节)
  • 25 = (200 - 100)/4 (short*** 数据宽度为4字节)
  • 25 = (200 - 100)/4 (int*** 数据宽度为4字节)

结果为相减后再除以 去掉一个*后的数据宽度

总结

指针之间的减法,其结果为相减后再除以去掉一个*后的数据宽度

指针之间的比较

指针之间也支持相互比较,但也和上面指针类型相减一样,要求指针类型一致

例子

void function(){                
        char**** a;
        char**** b;

        a=(char****) 200;
        b=(char****) 100;

        if (a>b)
        {
                printf("a>b\n");
        }else{
                printf("a<=b\n");
        }

}

运行结果

在这里插入图片描述

结论

相同类型的指针之间是支持大小比较的

总结

  • 指针的数据宽度为4字节,或者说:无论是什么类型,在其后面加上(无论加几个\都一样)后其数据宽度都变为4字节

  • 指针数值支持进行加减,加减的结果=去掉一个*后的数据宽度 × 增减的数值

  • 指针之间支持减法但不支持加法,其结果为相减后再除以去掉一个*后的数据宽度

  • 指针之间支持比较大小,但要求进行比较的两个指针为相同类型

posted @ 2023-04-21 11:11  私ははいしゃ敗者です  阅读(6)  评论(0编辑  收藏  举报  来源