滴水逆向 03-02 指针5
何意味呀,b站视频不全去官网花1块钱买了课程,结果播放不了,无所谓了,根据课件和别的师傅的博客也不是不能看
字符数组与字符串
字符串:
1 | char name[] = "ABCDE"; //定义时可以省略数组大小,编译器会自己补上0x00 |
编译器会在最后面添加一个 00 作为字符串结束的标记
指针定义的字符串存在常量区,数组定义的会存在全局区(全局变量)/栈(局部变量)上
字符数组:
1 | char arr[6] = {'A','B','C','D','E','\0'}; //正儿八经的定义,结尾也可以直接写0 |
在使用字符数组来表示字符串的时候,要在结尾加上一个 ‘\0’ ,也可以不手动写让编译器自己加,但是一定要给这个空字符预留出位置,不留的话它就不知道该到哪里停,会导致缓冲区溢出等问题。当然如果字符数组只是用来存 char 类型数据的,那就无所谓了。
%s 的用法:打印一个字符串,直到内存为 0 为止
常量区
内存分布图:

哪个可以修改?哪个不能改?
1 | char* x = "china"; |
编译都可以过,但是上面的不能改,下面的能改,为什么?
char* x = "china"; 不允许改,因为指针定义的字符串存在常量区,常量区不允许修改;
char y[] = "china"; 可以改,因为数组定义的字符串有一份在全局变量区的副本,修改也是修改的这个位置的副本。
哪个可以修改?哪个不能改?
1 | void Function() { |
先看第一句:
1 | void Function() { |
我们知道默认开辟的栈空间是 40h,加上这个后是 44h,也就是额外给 char* 类型的 x 开了一个位置,而这个位置存储的就是 china 字符串的地址,至于这个字符串自己本身,是存在常量区的
再看第二句:
1 | void Function() { |

发现 eax 里存的也是 china 这个字符串的首地址,演示第一句时那个首地址与这个一样,都是 0042201C,说明第二句这个定义方式也是存到了常量区,但是为什么这种写法就能改直接用指针指却改不了呢?看一下汇编

可以看到它是先把 chin 四个放到 eax 里,然后存到 [ebp-8] 上,还剩一个 a,又用了个 cx,存到 [ebp-4] 上。相比上一种,多了一步常量区字符串的拷贝。
我的理解是,字符串通常是存在常量区的,只是使用数组字符的时候,会把常量区里的字符串拷贝到数据段(全局变量)/堆栈(局部变量)这些可以修改的地方,修改时也是直接修改拷贝的这份副本,常量区原本的那份也是不会变的。
常见字符串操作
-
返回字符串的长度
int strlen (char* s)返回值是字符串 s 的长度,不包括结束符 ‘\0’1
2
3
4
5
6
7
8int strlen(char* s) {
int ret = 0;
while (*s != 0) {
ret++;
s++;
}
return ret;
} -
复制字符串
char* strcpy (char* dest, char* src)复制字符串 src 到 dest 中,返回指针为 dest 的值1
2
3
4
5
6
7//c h i n a 0
//x y z 0
char* strcpy(char* dest, char* src) {
char* ret = dest;
while((*dest++) = (*src++));
return ret; //dest++了一轮后指向了最后一个位置,所以要额外用一个ret来代表开头的位置
}这样的操作会把 src 字符串里的 ‘\0’ 结束符也复制过去,因为 while 语句里面是先赋值再判断的,所以在遇到 0 停止之前,0 就已经赋值过去了
-
字符串的拼接
char* strcat (char* dest, char* src)将字符串 src 添加到 dest 尾部,返回指针为 dest 的值1
2
3
4
5
6
7
8
9//c h i n a 0
//x y z 0
//c h i n a x y z 0
char* strcat(char* dest, char* src) {
char* ret = dest;
while (*dest != 0) dest++; //注意不要写成*dest++!=0,因为++会导致后移一位,之前0的位置接不上,就会导致后续遇到0就停了拼接了个寂寞
while ((*dest++) = (*src++));
return ret;
} -
字符串比较
int strcmp (char* s1, char* s2)一样返回 0,不一样返回 11
2
3
4
5
6
7
8
9
10//c h i n a 0
//c h i n a x y z 0
int strcmp(char* s1, char* s2) {
while (*s1 && *s2 && (*s1 == *s2)) {
s1++;
s2++;
}
if (*s1 == 0 && *s2 == 0) return 0;
return 1;
}
课后作业
模拟实现CE的数据搜索功能:这一堆数据中存储了角色的名字信息(WOW),请列出角色名的内存地址
1 | 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09, |
-
编写函数,返回角色名字信息的地址,如果没有返回 0
char* FindRoleNameAddr(char* pData, char* pRoleName)1
2
3
4
5
6
7
8
9
10
11
12
13int strcmp(char* s1, char* s2) {
while (*s2 && (*s1 == *s2)) {
s1++;
s2++;
}
if (*s2 == 0 && *s1 == 0) return 0; //要判断==0是因为可能会存在WOWX也判断为WOW的情况,所以s1也得是个\0才可以
return 1;
}
char* FindRoleNameAddr(char* pData, char* pRoleName){
if(!strcmp(pData, pRoleName)) return pData;
return 0;
} -
编写函数,遍历上面数据中所有角色名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
char data[100] = {
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
};
int strlen(char* s) {
int ret = 0;
while (*s != 0) {
ret++;
s++;
}
return ret;
}
int strcmp(char* s1, char* s2) {
while (*s2 && (*s1 == *s2)) {
s1++;
s2++;
}
if (*s2 == 0 && *s1 == 0) return 0;
return 1;
}
char* FindRoleNameAddr(char* pData, char* pRoleName){
if(!strcmp(pData, pRoleName)) return pData;
return 0;
}
int main(int argc, char* argv[]) {
char* name = "WOW";
int len = strlen(name);
for (int i = 0; i < 100 - len; i++) { //因为结尾也要配上\0,所以多检查一个字节
char* addr = FindRoleNameAddr(data + i, name);
if (addr != 0) {
printf("%p\n", addr);
}
}
return 0;
}