今天下雪了,所以我来看雪!!!
最近两个月我木有发帖,学校里的事比较多哈。。。第14届安徽省大学生职业规划大赛(金奖)主题就是软件逆向工程师(评委:这是啥???)。然后就是国家奖学金等。不亏,给
钱让我上大学。。。
好的,装0xD完了。进入正题。
首先我要解释一个梗:指针就是地址??? 不完全是!
指针难么?
首先我们来看指针的宽度是啥。
char a=1;
char* b=(char*)1; //下面解释
char** c=(char**)1;
a的宽度是1个字节
b的宽度是4个字节(32位)
二级指针 c也是4???
啥是二级指针 ?指向一级指针的东西???弄弄弄! 不一定是
继续,
short y;//宽度2字节
int z;//宽度4字节(32位机)
float f;//宽度4(32位机)
double d;//宽度8
加个*会怎样???
char* x;
short* y;
int* z;
float* f;
double* d;
2020年我国全面进入小康社会,所有屌丝都有女朋友!!!人人平等!
咳咳,跑题了。。。它们的宽度都是4个字节,不信?你看!
14: char* x=(char*)10;
00401048 C7 45 FC 0A 00 00 00 mov dword ptr [ebp-4],0Ah
15:
16: short* y=(short*)10;
0040104F C7 45 F8 0A 00 00 00 mov dword ptr [ebp-8],0Ah
17:
18: int* z=(int*)10;
00401056 C7 45 F4 0A 00 00 00 mov dword ptr [ebp-0Ch],0Ah
19:
20: float* f=(float*)10;
0040105D C7 45 F0 0A 00 00 00 mov dword ptr [ebp-10h],0Ah
21:
22: double* d=(double*)10;
00401064 C7 45 EC 0A 00 00 00 mov dword ptr [ebp-14h],0Ah
:
那么,二级指针是啥?恩。。。
14: char** x=(char**)10;
00401048 C7 45 FC 0A 00 00 00 mov dword ptr [ebp-4],0Ah
15:
16: short** y=(short**)10;
0040104F C7 45 F8 0A 00 00 00 mov dword ptr [ebp-8],0Ah
17:
18: int** z=(int**)10;
00401056 C7 45 F4 0A 00 00 00 mov dword ptr [ebp-0Ch],0Ah
19:
20: float** f=(float**)10;
0040105D C7 45 F0 0A 00 00 00 mov dword ptr [ebp-10h],0Ah
21:
22: double** d=(double**)10;
00401064 C7 45 EC 0A 00 00 00 mov dword ptr [ebp-14h],0Ah
恩,也是4个字节! 你爹有5个亿,你有女票。我爹是画家,我也有女票!
总结:普通类型的指针宽度都是4个字节。多级指针也是4个字节。
下面我们看看一些奇葩的指针。
1. 指针数组(是指针的数组,是数组!)
为啥? char[10]的宽度是10个字节,因为char宽度*10,那么char* [10]的宽度就是10个char*的宽度。也就是40。
人人平等,人人都有女票!
也是40个字节
那么 两个星呢?
恩,也是40个字节。
2 结构体指针
也是4个字节。 为啥? 因为这个指针通常指向了结构体变量的首地址(这个不能乱用)
3 函数指针
16: int (*pf)(int)=f;
00401088 C7 45 FC 0F 10 40 00 mov dword ptr [ebp-4],offset @ILT+10(f) (0040100f)
0040108F 8B 45 FC mov eax,dword ptr [ebp-4]
00401092 89 45 FC mov dword ptr [ebp-4],eax
宽度也是4,因为它现在指向了函数的首地址(其实是跳转表)。
指针运算
指针在做运算时,要拿砍掉一个星后的宽度然后在算。这样的运算规则便于开发寻址。
a的类型char* 宽度 砍掉一个*,变成了char。然后char的宽度是1。100+1*5=105.
以此类推。
自加
减法同理
指针与指针的运算
指针主要是用于寻址的。所以,我们假设a指向了一个变量,b也指向了一个变量,所以相减就是他们的地址差。指针与指针的运算应该先减,然后除以砍掉一个*的宽度。
同理,a-b=100,100/int的宽度4字节,最后得25。
多级指针与多级指针的运算
因为普通类型的指针宽度都是4个字节(32位机),所以我就举着一个例子吧。。
引用&的用法
引用的本质就是取地址,比如:
10: char a = 10;
00401048 C6 45 FC 0A mov byte ptr [ebp-4],0Ah
11: short b = 20;
0040104C 66 C7 45 F8 14 00 mov word ptr [ebp-8],offset main+20h (00401050)
12: int c = 30;
00401052 C7 45 F4 1E 00 00 00 mov dword ptr [ebp-0Ch],1Eh
13:
14: char* pa = (char*)&a;
00401059 8D 45 FC lea eax,[ebp-4]
0040105C 89 45 F0 mov dword ptr [ebp-10h],eax
15: short* pb = (short*)&b;
0040105F 8D 4D F8 lea ecx,[ebp-8]
00401062 89 4D EC mov dword ptr [ebp-14h],ecx
16: int* pc = (int*)&c;
00401065 8D 55 F4 lea edx,[ebp-0Ch]
00401068 89 55 E8 mov dword ptr [ebp-18h],edx
指针与引用正常情况下可以看成是一对,也就是间接寻址的引用。
比如说
10: char a = 10;
00401048 mov byte ptr [ebp-4],0Ah
11:
12:
13: char* pa = (char*)&a;//取地址
0040104C lea eax,[ebp-4]
0040104F mov dword ptr [ebp-8],eax
14:
15:
16: printf("%d ",*pa);
00401052 mov ecx,dword ptr [ebp-8]//取ebp-8的数据 然后把它当成地址来取里面的值。
00401055 movsx edx,byte ptr [ecx]
00401058 push edx
00401059 push offset string "%d " (0042e01c)
0040105E call printf (00408160)
00401063 add esp,8
17:
说的简单粗暴点就是:变量前面加一个*就是把这个变量里面的值当成地址,然后取这个地址里面的值(相当于砍掉一个*)。 变量前面加一个&就是取这个变量的地址(相当于加一个星)。
例题:
打印数组
char arr[10];
char* p = &arr[0]; //取数组第一个元素的地址
for(int k=0;k<10;k++)
{
printf("%d\n",*(p+k)); //取地址里的值
}
对应反汇编
12: char* p = &arr[0]; //取数组第一个元素的地址
00401048 lea eax,[ebp-0Ch]
0040104B mov dword ptr [ebp-10h],eax
13: for(int k=0;k<10;k++)
0040104E mov dword ptr [ebp-14h],0
00401055 jmp main+30h (00401060)
00401057 mov ecx,dword ptr [ebp-14h]
0040105A add ecx,1
0040105D mov dword ptr [ebp-14h],ecx
00401060 cmp dword ptr [ebp-14h],0Ah
00401064 jge main+4Fh (0040107f)
14: {
15: printf("%d\n",*(p+k)); //取地址里的值
00401066 mov edx,dword ptr [ebp-10h]//数组首地址
00401069 add edx,dword ptr [ebp-14h]//k变量
0040106C movsx eax,byte ptr [edx]//寻址
0040106F push eax
00401070 push offset string "%d\n" (0042e01c)
00401075 call printf (00408180)
0040107A add esp,8
16: }
0040107D jmp main+27h (00401057)
17:
指针类型
虽然指针的宽度都是4字节,但是用的时候还是与区别的.
char类型的指针一次访问内存一个字节。
Short 类型的指针一次访问内存两个字节
Int 类型的指针一次访问内存4个字节
比如:
反汇编代码:
11: int i=300;
00401048 mov dword ptr [ebp-4],12Ch
12: char* b=(char*)&i;
0040104F lea eax,[ebp-4]
00401052 mov dword ptr [ebp-8],eax
13:
14: printf("%d",*b);
00401055 mov ecx,dword ptr [ebp-8]
00401058 movsx edx,byte ptr [ecx] //这里 ,只取了1个byte
300的二进制是100101100 而char*只能取1个字节的数据,所以取低8位,也就是101100
=42。
例题
模拟实现CE的数据搜索功能:
这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100(10进制)
请列出所有可能的值以及该值对应的地址.
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,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 f(char a[],int l)
{
for(int i=0;i<l;i++)
{
if(*(int*)(a+i)==0x64) //十进制100
printf("%0x\n",&a[i]);
}
}
int main()
{
char a[]=
{
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,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
};
int l=sizeof(a)/sizeof(a[0]); //数组长度
f((char*)a,l);
return 0;
}
核心算法反编译代码:
11: for(int i=0;i<l;i++)
00401048 mov dword ptr [ebp-4],0
0040104F jmp f+2Ah (0040105a)
00401051 mov eax,dword ptr [ebp-4]
00401054 add eax,1
00401057 mov dword ptr [ebp-4],eax
0040105A mov ecx,dword ptr [ebp-4]
0040105D cmp ecx,dword ptr [ebp+0Ch]
00401060 jge f+53h (00401083)
12: {
13:
14: if(*(int*)(a+i)==0x64)//
00401062 mov edx,dword ptr [ebp+8]//取数组的地址(参数1)
00401065 add edx,dword ptr [ebp-4]//+i
00401068 cmp dword ptr [edx],64h
0040106B jne f+51h (00401081)
15: printf("%0x\n",&a[i]);
0040106D mov eax,dword ptr [ebp+8]
00401070 add eax,dword ptr [ebp-4]
00401073 push eax
00401074 push offset string "%0x\n" (0042f01c)
00401079 call printf (004083e0)
0040107E add esp,8
16:
17: }
00401081 jmp f+21h (00401051)
18:
字符串与字符数组
字符串其实是一个指针,指向常量区(不可写数据段)的对应字符。字符数组就是把常量区的对应字符拷贝过来(类似于内存拷贝)。
比如:
char arr[6] = {'A','B','C','D','E','\0'};
char names[] = "ABCDE";
printf("%s\n",arr);
printf("%s\n",names);
反汇编代码:
00401048 mov byte ptr [ebp-8],41h
0040104C mov byte ptr [ebp-7],42h
00401050 mov byte ptr [ebp-6],43h
00401054 mov byte ptr [ebp-5],44h
00401058 mov byte ptr [ebp-4],45h
0040105C mov byte ptr [ebp-3],0
上面是 char arr[6]
00401060 mov eax,[string "ABCDE" (0042f020)]
00401065 mov dword ptr [ebp-10h],eax
00401068 mov cx,word ptr [string "ABCDE"+4 (0042f024)]
0040106F mov word ptr [ebp-0Ch],cx
上面是 char names[]
00401073 lea edx,[ebp-8]
00401076 push edx
00401077 push offset string "%s\n" (0042f01c)
0040107C call printf (004081a0)
00401081 add esp,8
00401084 lea eax,[ebp-10h]
00401087 push eax
00401088 push offset string "%s\n" (0042f01c)
0040108D call printf (004081a0)
00401092 add esp,8
00401095 xor eax,eax
00401097 pop edi
00401098 pop esi
00401099 pop ebx
0040109A add esp,50h
0040109D cmp ebp,esp
0040109F call __chkesp (00408220)
004010A4 mov esp,ebp
004010A6 pop ebp
004010A7 ret
指针数组
指针数组就是指针的数组,通常用来存放指针
比如:
int a=10;
int b=20;
int c=30;
int d=40;
int e=50;
int* arr[5]={&a,&b,&c,&d,&e};
for(int i=0;i<5;i++)
{
printf("%d\n",*(arr[i]));
}
反汇编代码
12: int a=10;
00401048 mov dword ptr [a],0Ah
13: int b=20;
0040104F mov dword ptr [b],14h
14: int c=30;
00401056 mov dword ptr [c],1Eh
15: int d=40;
0040105D mov dword ptr [d],28h
16: int e=50;
00401064 mov dword ptr [e],32h
17:
18: int* arr[5]={&a,&b,&c,&d,&e};
0040106B lea eax,[a]
0040106E mov dword ptr [arr],eax
00401071 lea ecx,[b]
00401074 mov dword ptr [ebp-24h],ecx
00401077 lea edx,[c]
0040107A mov dword ptr [ebp-20h],edx
0040107D lea eax,[d]
00401080 mov dword ptr [ebp-1Ch],eax
00401083 lea ecx,[e]
00401086 mov dword ptr [ebp-18h],ecx
19:
20: for(int i=0;i<5;i++)
00401089 mov dword ptr [i],0
00401090 jmp main+6Bh (0040109b)
00401092 mov edx,dword ptr [i]
00401095 add edx,1
00401098 mov dword ptr [i],edx
0040109B cmp dword ptr [i],5
0040109F jge main+8Ah (004010ba)
21: {
22: printf("%d\n",*(arr[i]));
004010A1 mov eax,dword ptr [i]
004010A4 mov ecx,dword ptr arr[eax*4]//int是4个字节
004010A8 mov edx,dword ptr [ecx]
004010AA push edx
004010AB push offset string "%d\n" (0042f01c)
004010B0 call printf (004081d0)
004010B5 add esp,8
23: }
004010B8 jmp main+62h (00401092)
结构体指针
这种指针通常情况下是指向结构体变量的首地址,用的时候小心点。这个容易发生缓冲区溢出错误。
例题
查找这些数据中,有几个id=1 level=8的结构体信息。
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x01,0x00,0x00,0x00,0x08,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
结构体定义如下:
typedef struct TagPlayer
{
int id;
int level;
}Player;
答案
typedef struct TagPlayer
{
int id;
int level;
}Player;
Player* f(char* p,int a,int b,int size)
{
if(size==0)//遍历完所有的数据
return 0;
if(*(int*)p==1 && *(int*)(p+4)==8) //p是char 所以先+4个字节再用int*读数据
{
printf("%0x\n",p);
f(++p,a,b,size--);
}
//printf("%0x %0x\n",*(int*)p,*(int*)(p+4));
f(++p,a,b,size--);
}
int main()
{
char a[]={
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00,
0x00,0x33,0x01,0x00,0x00,0x08,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00,
0x00,0x00,0x64,0x01,0x00,0x00,0x00,0x08,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
};
f(a,1,8,sizeof(a)/sizeof(a[0]));
return 0;
}
查找函数的核心算法的反编译代码:
数组指针
。。。我还是发文件上来吧,感冒了,头痛。 指针资料.rar里还有我写的很多笔记,实验等
[2020元旦礼物]《看雪论坛精华17》发布!(补齐之前所有遗漏版本)!
最后于 10小时前 被AMask编辑 ,原因: