【滴水逆向笔记】C语言指针



一、带*类型变量的赋值

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.指针可以指向任何数据类型,不是说一定指针就只能指向本身的类型

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值