文章目录
一、带*类型变量的赋值
int* x; //把int*看成一种新的数据类型
下面赋值的时候一定要写全
x = (int*)10;
可以加入多个*
int*** x;
x = (int***) = 10;
带‘*’的类型反汇编都是4个字节
带*号的可以直接加减
char* x;
short* y;
int* z;
x = (char*)100;
y = (short*)100;
z = (int*)100;
x++;
y++;
z++;
printf("%d,%d,%d",x,y,z);
打印结果:101,102,104
,也就是说,根据自身类型的宽度来加
char** x;
short** y;
int** z;
x = (char**)100;
y = (short**)100;
z = (int**)100;
x++;
y++;
z++;
printf("%d,%d,%d",x,y,z);
打印结果:104,104,104
公式:去掉一个“”的长度,就是它加的长度,比如char*
,去掉一个就是char,一个字节,比如char**
,去掉一个“*”还有一个,只要有一个就是4字节
以前学过的任何一个数据类型都可以加一个星号,这样就是一个完全不同的类型,不管一个星还是几个星,都是四个字节
char* x;
short* y;
int* z;
x = (char*)100;
y = (short*)100;
z = (int*)100;
x=x+5;
y=y+5;
z=z+5;
printf("%d,%d,%d",x,y,z);
打印结果:105,110,120
公式:砍掉一个后,剩下的乘以5,比如int*
,砍掉‘’后是4,4X5=20,所以是120
char***** x;
short***** y;
int***** z;
x = (char*****)100;
y = (short*****)100;
z = (int*****)100;
x=x+5;
y=y+5;
z=z+5;
printf("%d,%d,%d",x,y,z);
打印结果:120,120,120
char* x;
short* y;
x = (char*)200;
y = (short*)100;
int a = x-y;
printf("%d",a);
这里必须要类型相同才能减,包括*号数量也必须一样,不然会报错
打印结果:100
char** x;
short** y;
x = (char**)200;
y = (short**)100;
int a = x-y;
printf("%d",a);
打印结果:25
第一次因为是char,200-100就是100,实际上就是100/4,就是减完以后,砍掉一颗*,然后除以这个数据的长度
带*的可以与一个整数相加相减,还可以求差值
转换类型
char* x;
int* y;
x = (char*)10;
y = x;
这样直接报错了
char* x;
int* y;
x = (char*)10;
y = (int*)x;
拓展:char和int为什么可以转?因为char8位,int32位,转int前面填0就行,int转char前面的不要就行,如果是结构体转基本类型的话,他不知道结构体在哪里,转不了
struct student()
{
int age;
int level;
};
void Test()
{
char* x;
student* y;
x = (char*)10;
y = (student*)x;
}
编译通过,转得了
struct student()
{
int age;
int level;
};
void Test()
{
char* x;
char** y;
x = (char*)10;
y = (char**)x;//1
//y = x;这个编译不通过,语句2
}
如图,语句一可以转,语句2不可以
二、&符号
&是地址符,任何一个变量都可以用&来获取地址,比如&a可以获取到a的地址
char x;
short y;
int* z;
x = 10;
y = 20;
z = 30;
//char x = &a 编译失败,报错不能把char*转成char
也就是说&a的类型就是a的类型加一颗’*’就是char*
,&b的类型short*
char x;
short y;
int* z;
x = 10;
y = 20;
z = 30;
char* px;
px = = &x;//断点
反汇编
ebp-4就是x,就是10,lea就是取ebp-4的地址,给eax,然后把这个值给ebp-10,就是这个px局部变量
int a;
int* pa;
a = 10;
pa = &a;//&x需要char*类型的容器,其实就是pa = (int*)&a,不要那个(int*)
反汇编
struct student()
{
int age;
int level;
};
int x = 10;
void Test()
{
int* y = x;
}
这个地方存的是一个A
执行后的效果:
也就是说&的需要在原来类型的基础上加个*
int x = 10;
int* px = &x;
int x1 = *px
如果在一个变量前用‘*’,比如
int* x *x == int;
得到的结果就是原来类型砍一棵星
反汇编:
三、数组指针
void test()
{
char arr[5] = {1,2,3,4,5,6};
//用char* x = &arr[0]; == char& px = arr;
char* px = arr;//px存的就是第一个整数的地址
printf('%x',px);
}
打印结果:12ff24,如果要打印数组的值
void test()
{
char arr[5] = {1,2,3,4,5,6};
//用char* x = &arr[0]; == char* px = arr;
char* px = arr;//px存的就是第一个整数的地址
printf('%d',*px);
}
打印出1
如果char* p = 10,那么p+1=11,11指向的就是p[1];
所以要打印出arr所有的值
void test()
{
char arr[5] = {1,2,3,4,5,6};
//用char* x = &arr[0]; == char* px = arr;
char* px = arr;//px存的就是第一个整数的地址
for(char i =0;i<=5;i++)
{
printf('%d\n',*(px+i));
}
}
打印结果:
1
2
3
4
5
四、CE初探
如左边,是我们的地址,右边是值
扫描2字节的2:
点击new scan
搜索类型中的bigger than,就是多少个字节比than大
value between 2~6之间
现在搜索的是哪些地址的值是2
现在在游戏里,找一堆数据里可能是血量的值,血量为100
char data[100] =
{
0x00,0x64,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
void findblood()
{
printf('%x\n',&data[0]);
for(int i = 0;i<100;i++)
{
if(data[i] == 100)
{
printf('%x\n',&data[i]);
}
}
}
这样就能找到地址,有五个0x64的地址,在这前会打印data【0】的地址,而第一个0x64
的地址就是data【0】的地址加上1
如果是int类型的话,下面要改动,用上指针
char data[100] =
{
0x00,0x64,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
void findblood()
{
int* p = (int*)&data[0];
printf('%x\n',&data[0]);
for(int i = 0;i<25;i++)
{
if(*(p+i) == 100)
{
printf('%x\n',(p+i));
}
}
}
发现报错,这里应该用一种新算法,取出前四个,然后看是不是有等于100的,然后再从第二个往后取,也就是顺序是1,2,3,4然后是2,3,4,5。因为这些数据不一定四个拼在一起的时候刚好是一个int。内存的事情是说不准的,所以上述程序是错的
char data[100] =
{
0x00,0x64,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
void findblood()
{
int* p = (int*)&data[0];
for(int i = 0;i<25;i++)
{
printf('%d\n',*(p+i));
}
}
可以打印出来地址里的值,如果要两个字节地查
char data[100] =
{
0x00,0x64,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
void findblood()
{
short* p = (short*)&data[0];
for(int i = 0;i<50;i++) //现在是两个两个查的,所以小于50
{
printf('%d\n',*(p+i));
}
}
打印结果前两个:6400,302
四个四个查
char data[100] =
{
0x00,0x64,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
void findblood()
{
short* p = (short*)&data[0];
for(int i = 0;i<25;i++) //现在是四个四个查的,所以小于25
{
printf('%d\n',*(p+i));
}
}
3026400,7060504,就是四个字节四个字节遍历,但要注意这里是特殊情况,很有可能是第二个第三个第四个第五个构成第一个int,虽然搜索是从第一个四个四个搜,但是内存分配的情况我们是说不准的
五、字符串指针
exe的内存分布:
可读可写:可以从里面读出来,可以修改里面的值,提一嘴,全局变量区还有可读可写的
可读不可写:常量区,不能改的
字符串的指针,比如‘china’
char* p = 'china';
这里默认指向的是C,也就是开头
这里看一下两种方法:
char* x = 'china';
char y[] = 'china';
void test()
{
*(x+1)='A';
y[1] = 'A';
}
注释掉y[1] = ‘A’
编译时没有问题,但在运行时有问题
这个报错是内存非法读写
注释掉*(x+1)= ‘A’,没有问题
原因:第一种写法,x存的是china的首地址,x能改,但是x指向的值不能改,因为x就是一个char*
的类型,x指向的值不能改,因为指针指向的值存在于常量区,为什么char y[]可以改,因为它是全局变量区
把void function()
里面的值都注释掉,给了0x40个空间,如果写char* x,44个空间,那如果写char* x = 'china';
呢?
还是ebp-4,但是这个x存的地址是0042201c,而字符串本身存在常量区
如果是char y[]=‘china’;
多分了8个
这样写,也是把china放到常量区
这里eax存入了china的ascii码
然后给到ebp-8
但是还是缺少了一个a,就存入到cx,然后放到cx里面,也就是说拷贝到堆栈里
第一条,我们修改的是常量区数据
第二条,它修改的数据已经拷贝到堆栈了,所以可以
出现005的报错可以参考
1.实现strlen判断长度
int strlen(char* s)
{
int ret = 0; //定义返回值
while(*s != 0) //取S首地址,s!=0时执行循环
{
ret++; //数量加1
s++; //指针后移
}
return ret;
}
2.实现strcpy复制长度
char strcpy(char* dest,char* src)
{
while(*dest != 0)
{
*dest = *src;
dest++;
src++;
}
char ret = dest;
return ret;
}
海哥一行代码结束。。。
如果src是0,那么就把0给了dest,这里就是先赋值再判断,因为已经过去了,所以*dest=0,就会断开循环
3.strcat字符串拼接
char strcat(char*dest,char*src)
{
char ret;
while(*dest != 0)
{
dest++;
}
while(*src != 0)
{
*dest = *src
dest++;
src++;
}
}
海哥的写法
思路也是一样的。。就是让dest指针先移动到末尾,然后就是复制函数的内容了
4.strcmp函数比较,一样返回0,不一样返回1
int strcmp(char* s1, char* s2)
{
while(*s1++ = *s2++)
{
if(*s1 == 0 || *s2==0)
{
return 1;
}
}
return 0;
}
5.指针函数,查找wow字符串地址
指针函数就是返回值为带*类型的函数
char arr[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,
0x00,0x02,0x57,0x4F,0x57,0x00,0x06,0x08,0x00,0x00,
0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00,
0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
char Name = 'WOW'
char* FindRoleNameAddr(char* data, char* RoleName)
{
char* start = RoleName;
for(int i = 0;i<100;i++)
{
if(*data == *RoleName)
{
data++;
Name++;
}
if(*RoleName == 0)
{
return data;
}
if(*data != *RoleName;)
{
RoleName = start;
*data++;
}
}
return 0;
}
int main()
{
printf("%x",FindRoleNameAddr(arr,Name));
return 0;
}
六.指针数组
指针数组在内存中是这样分配的
char arr[] = {'c','h','i','n','a'};
printf('%s\n',arr);
s是给定一个地址,让地址往后打印,直到见到0
出现地址
char arr[6] = {'c','h','i','n','a','\0'};
printf('%s\n',arr);
所以要往后补0
可以打印
char* p1 = 'if';
char* p2 = 'for';
char* p3 = 'while';
char* p4 = 'switch';
char* keyword[] = {'if','for','while','switch'};
这就是指针数组
七、结构体指针
struct Arg
{
int a;
int b;
int c;
}
int main()
{
Arg* pArg;
pArg = (Arg*)100;
}
反汇编是一样,但是运算方法不一样
struct Arg
{
int a;
int b;
int c;
}
int main()
{
Arg*** pArg;
pArg = (Arg***)100;
pArg++;
printf("%d\n",pArg)
}
答案为104
struct Arg
{
int a;
int b;
int c;
}
int main()
{
Arg* pArg;
pArg = (Arg*)100;
pArg++;
printf("%d\n",pArg)
}
打印结果:112
还是砍星成哪个类型的问题,前面是*,还是104
struct Arg
{
int a;
int b;
int c;
}
int main()
{
Arg* pArg;
pArg = (Arg*)100;
pArg=pArg+5;
printf("%d\n",pArg)
}
打印结果:160
struct Arg
{
int a;
int b;
int c;
}
int main()
{
Arg* pArg;
pArg = (Arg*)100;
pArg2 = (Arg*)20;
pArg-pArg2;
printf("%d\n",pArg)
}
打印结果:6,因为80
打印结果6
反汇编:
结构体指针不能用点,用箭头
struct Arg
{
int a;
int b;
int c;
}
int main()
{
Arg s;
s.a = 10;
s.b = 20;
s.c = 30;
Arg* px = &S;
printf("%d\n",px->a);
return 0;
}
打印结果:10
struct Arg
{
int a;
char b;
int c;
}
int main()
{
Arg s;
s.a = 10;
s.b = 20;
s.c = 30;
Arg* px = &S;
printf("%d %d %d\n",px->a,px->b,px->c);
return 0;
}
打印结果:10,20,30
也可以修改
struct Arg
{
int a;
char b;
int c;
}
int main()
{
Arg s;
s.a = 10;
s.b = 20;
s.c = 30;
Arg* px = &S;
printf("%d %d %d\n",px->a,px->b,px->c);
px->a = 100;
px->b = 200;
px->c = 300;
printf("%d %d %d\n",px->a,px->b,px->c);
return 0;
}
打印结果:
10 20 30
100 -56 300
因为这个char,越界了,char最多127
int x = 10;
Arg* px = (Arg*)&x;
printf("%d %d %d\n",px->a,px->b,px->c);
如果是这样的话,可能在内存中是10 00 00 00,所以第一个值会打印10,但是后面可能是一堆乱码
八.函数指针
#include "stdafx.h"
#include<stdlib.h>
int Function(int x,int y)
{
return x+y;
}
int main(int argc, char* argv[])
{
int(*pfun)(int,int);
Function(1,2);
return 0;
}
查看反汇编
如图,把硬编码转换成汇编
刚开始function存在代码区,而这个undesigned存在数据区,把前面的硬编码取出来,放到arr
#include "stdafx.h"
#include<stdlib.h>
unsigned char arr[] =
{
0x55,
0x8B, 0xEC,
0x83, 0xEC, 0x40,
0x53,
0x56,
0x57,
0x8D, 0x7D, 0xC0,
0xB9, 0x10, 0x00, 0x00, 0x00,
0xB8, 0xCC, 0xCC, 0xCC, 0xCC,
0xF3, 0xAB,
0x8B, 0x45, 0x08,
0x03, 0x45, 0x0C,
0x5F,
0x5E,
0x5B,
0x8B, 0xE5,
0x5D,
0xC3
};
int main(int argc, char* argv[])
{
int(*pfun)(int,int);
pfun = (int(*) (int,int))&arr;
int x = pfun(2,3);
printf("%d\n",x);
system("pause");
return 0;
}
执行了之前function的函数
用处:把所有保护的函数,从生成完的文件抠出来,放到自己定义的一个数组里,然后在数组上做一些手脚,隐藏起来,把所有存储函数的地方全部存0
总结
实际上记住三点
1.指针别看成指针,就是一些新的数据类型
2.掌握指针的加加减减
3.指针可以指向任何数据类型,不是说一定指针就只能指向本身的类型