指向指针的指针

  • 占有内存空间就有地址,有地址就可以被指针指向,如果指针作为一个参数,那么改变它就需要指针的地址,指针的指针在这种场景下就应孕而生

注意,命令double *pp = &p;

在c++中编译错误,在c中也会产生警告信息

void main()
{
    int earning = 12000;
    double *p = &earning;
    //double *pp = &p;//p的地址为4个字节,pp指向的数据类型double占用8个字节
    //pp++执行后前进8个字节 p++前进4个字节,不匹配所以,此行代码非法
    printf("p的大小是%d,double类型大小%d\n", sizeof(p), sizeof(double));
    double**pp = &p;//这样引入了一个崭新的数据类型(double*)double指针类型
    printf("p的大小是%d,double*  指针类型大小%d\n", sizeof(p), sizeof(double*));
    system("pause");
}

 

 一级指针装载变量的地址

二级指针装载指针地址,由此我们可以改变指针的指向,游戏外挂中使用很多。

  • 模拟游戏外挂原理
第一步:

准备一段代码模拟苏联图-128截击机的载油情况,这段代码试图捕捉到代码执行过程中各关键变量的地址,然后交给指针的指针修改 #include<stdio.h> #include<stdlib.h> void main() { int fuelindex = 100; int fuelindex_m = 66; int fuelindex_l = 23; printf("fuelindex:%x\n",&fuelindex); printf("fuelindex_m:%x\n", &fuelindex_m); printf("fuelindex_l:%x\n", &fuelindex_l); char fuellevelhigh = 'h', fuellevelmedium='m', fuellevellow='l'; printf("高级载油量地址为:%x\n", &fuellevelhigh); printf("中级载油量地址为:%x\n", &fuellevelmedium); printf("低级载油量地址为:%x\n", &fuellevellow); char *fuellevel = &fuellevelhigh; system("pause"); }

输出结果:

 

 第二步:编写模块

如果你使用了微软的vs2013集成开发环境,选中解决方案----添加----新建项----visual C++-----常规-----空项目,命名完成后,选中源文件----添加----新建项,开启一个.c文件,我命名为gametrick.c,

然后选中gametrick.c----属性----配置属性----目标文件扩展名选择.dll

往这个文件写入一个函数,在函数体之前添加 _declspec(dllexport);(尽管到目前为止,我也弄不清这是什么,但是就先加上吧)

_declspec(dllexport) void go()
{
    //int *p = 0xcffbcf;//这样还不够,因为仅仅知道表示地址的值0xcffbcf,但并不知道是什么类型
    //也就无从知道执行前进操作时,前进几步
    //所以
    int *p = (int *)0x12ff964;
    *p = 87;
}

写完后在IDE 找到生成--生成,这样就生成了一个动态链接库文件

 

 

接下来运行你的主程序,打开DLL注入文件,选中主程序文件对应的那个进程

#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>


void main()
{
    int fuelindex = 100;
    int fuelindex_m = 66;
    int fuelindex_l = 23;
    printf("fuelindex:%x\n",&fuelindex);
    printf("fuelindex_m:%x\n", &fuelindex_m);
    printf("fuelindex_l:%x\n", &fuelindex_l);

    char fuellevelhigh = 'h', fuellevelmedium='m', fuellevellow='l';
    printf("高级载油量地址为:%x\n", &fuellevelhigh);
    printf("中级载油量地址为:%x\n", &fuellevelmedium);
    printf("低级载油量地址为:%x\n", &fuellevellow);
    char *fuellevel = &fuellevelhigh;
    while (1)
    {
        printf("\n载油量是%d,%c", fuelindex, fuellevelhigh);
        Sleep(3000);//为避免打印过于频繁,需要暂缓执行,引入windows.h函数
    }
    system("pause");
}

注入,生成的模块

 

 

 

 看到图中第一行显示的载油量地址为 12ff964,添加到模块文件时,需要改为0x12ff964

 

 通过指针的指针改变值,不知你是否也这样倒霉,弄了一下午,就是改不对

_declspec(dllexport) void cpc()
{
    //char*p = 0xcffbcf;//这样还不够,因为仅仅知道表示地址的值0xcffbcf,但并不知道是什么类型
    //也就无从知道执行前进操作时,前进几步
    //所以
    char **p = (char **)0xeffd47;
    *p = (char*)0xeffd3b;
}

这种改法,感觉很不稳定

 

 总之指针的指针赋值的时候:

类型适配规范:

下列代码侥幸会成功

#include<stdlib.h>

void main()
{
    char mark = 'Z';
    char *tomark = &mark;
    char **tochar = tomark;
    printf("%x", tomark);
    system("pause");
}

而下列代码即使运行时不报错,也一定会导致异常结果

void main()
{
    double mark = 3.1415826;
    double *tomark = &mark;
    double *tochar = &tomark;
    printf("mark大小:%d,tomark大小:%d,tochar大小:%d\n", sizeof(double *), sizeof(&mark), sizeof(tomark));
    printf("tochar的结果:%lf\n",*tochar);
    getchar();
}

输出结果:

 

 

 

 究其原因是一个一级指针A变量,如果存入了另一个一级指针B的地址,而不声明类型的话,调用指针A(**A)时很可能因为不知道往前读几个字节而导致对值读取的失败。(也有观点认为会造成溢出,从而造成实际结果不准确)

有影像资料显示,2014年时,vs2013平台上该上机实验会导致报错,现在是2019年可能c语言的标准发生了一些改变,或者微软做了某些优化处理,但类型不匹配导致的数值不准确的风险的确存在!!!

void main()
{
    double mark = 3.1415826;
    double *tomark = &mark;
    double *tochar = (double *)tomark;//所以这一步的类型强转必不可少
    printf("tochar的结果:%lf\n", *tochar);
    getchar();
}

 输出结果:

 

 这种四舍五入是正常的,因为double只能容纳8位

这一问题也引出了C指针的使用原则,要避免指针存储与自身数据类型不同的值的情况(说人话就是别用字符型指针取存储整型值的地址,反之亦然),因为数据类型不同,分配内存大小不同

很可能出现读取不完整或者读取过量的情况,如果一个double*类型数据去读取一个char类型数据,看起来没什么问题,但别忘了内存中存在大量垃圾数据!也可能一并读进去了。

  • 同类型指针之间相互赋值
void main()
{
    int mynum = 8;
    int *hisnum = &mynum;//?木马的控制端
    int *hernum = hisnum;//相当于木马的被控制端?
    *hernum = 88;
    printf("数据通信,共用一个变量mynum:%d,*hisnum:%d,*hernum:%d\n", mynum, *hisnum, *hernum);
    getchar();
}
  • 为什么指针和要读取的数据要同一类型
void main()
{
    int mynum = 8;
    double *p = &mynum;
    printf("指针p指向%x,该地址上存的值为%f\n",p,*p);//这句注意为了再现错误,一定要用占位符%f
    getchar();
}

输出结果:

 

 这又引出一个重要原则,两个指针地址一样,只能说明首地址一样,但是别忘了指针类型不同,因此结束的地址并不相同(长度不同,前进距离不同)解析的方式也不一样,所以不能混用!!

  •  应用借助DLL注册工具----给列兵瓦洛佳授予军籍等级(小提示:使用外挂时,不从vs2013IDE运行主程序,因为编译外挂程序时,会中断运行中的主程序,而外挂需要运行中的变量的内存地址

从vs项目文件夹中运行主程序.exe)

#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
void main()
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = a;
    printf("数组的首地址%x,二级指针地址为%x\n", a,&p);

    while (1)
    {
        printf("当前军籍%d级\n", *p);
        Sleep(3000);
    }
    getchar();
}
外挂程序
_declspec(dllexport) void cpclovesme()
{
    int **p = (int **)0xaffe68;//这一地址是指向数组首元素地址的指针的地址,也就是主程序中的&p
    *p = (int*)(0xaffe74 + 36);//0xaffe74是数组首元素的地址,这行的意义在于让p指向数组的第10个元素的地址,因为整型占4个字节,9个单位就是9*4=36
}

运行结果

 

 

posted @ 2019-11-27 18:10  saintdingtheGreat  阅读(472)  评论(1编辑  收藏  举报