- 占有内存空间就有地址,有地址就可以被指针指向,如果指针作为一个参数,那么改变它就需要指针的地址,指针的指针在这种场景下就应孕而生
注意,命令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 ", sizeof(p), sizeof(double)); double**pp = &p;//这样引入了一个崭新的数据类型(double*)double指针类型 printf("p的大小是%d,double* 指针类型大小%d ", 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 ",&fuelindex); printf("fuelindex_m:%x ", &fuelindex_m); printf("fuelindex_l:%x ", &fuelindex_l); char fuellevelhigh = 'h', fuellevelmedium='m', fuellevellow='l'; printf("高级载油量地址为:%x ", &fuellevelhigh); printf("中级载油量地址为:%x ", &fuellevelmedium); printf("低级载油量地址为:%x ", &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 ",&fuelindex); printf("fuelindex_m:%x ", &fuelindex_m); printf("fuelindex_l:%x ", &fuelindex_l); char fuellevelhigh = 'h', fuellevelmedium='m', fuellevellow='l'; printf("高级载油量地址为:%x ", &fuellevelhigh); printf("中级载油量地址为:%x ", &fuellevelmedium); printf("低级载油量地址为:%x ", &fuellevellow); char *fuellevel = &fuellevelhigh; while (1) { printf(" 载油量是%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 ", sizeof(double *), sizeof(&mark), sizeof(tomark)); printf("tochar的结果:%lf ",*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 ", *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 ", mynum, *hisnum, *hernum); getchar(); }
- 为什么指针和要读取的数据要同一类型
void main() { int mynum = 8; double *p = &mynum; printf("指针p指向%x,该地址上存的值为%f ",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 ", a,&p); while (1) { printf("当前军籍%d级 ", *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
}
运行结果