memset 函数
- 函数原型:void *memset(void *str, int c, size_t n)
- 主要功能:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符
- C/C++ 实现:
#include <string.h>
#include <iostream>
#include <stdio.h>
using namespace std;
int main(int argc, char ** argv)
{
char str[20] = "AAAAAAAAA";
memset(str, 'v', 10);
cout << str << endl;
return 0;
}
- 以上程序的作用是循环复制 10 个字符 ‘v’ 到 str 所指向的字符串,执行结果:
- 函数运行步骤:
- 逆向分析:这个函数的作用主要是将特定的字符覆盖规定的字符串,所以首先需要取出传入 memset 函数的三个参数,并且判断第三个和第二个参数是否为 0。如果第三个参数为 0,则直接返回第一个参数所指向的字符串的首地址;如果第二个参数为 0 且第三个参数小于 100,则程序继续往下执行,反之跳出函数进行错误处理
- 之后判断第三个参数是否小于 4,为什么要进行这个判断呢,这个先不说答案在下面。然后判断参数一传入的字符串地址是否数据对齐,如果没有对齐就对其进行对齐操作
- 对齐判断后就开始复制字符串了,值得注意的是这里复制字符串的方式并不是循环复制 1 个字节,而是 4 个字节 4 个字节的复制,最后剩余的再每次 1 个字节的循环复制。比如我要复制 10 个字节,先循环两次复制 8 个字节,最后循环两次每次复制 1 个字节,还记得上面判断第三个参数为什么小于 4 吗,因为小于 4 的话就不必要每次 4 个字节的复制了,直接跳转到每次复制 1 个字节处即可
- 最后将覆盖完的字符串首地址放入 eax 中,函数返回
- 总结:memset 流程图
strcpy 函数
- 函数原型: char *strcpy(char *dest, const char *src)
- 作用:把 src 所指向的字符串复制到 dest
- C/C++ 实现:
#include <string.h>
#include <iostream>
#include <stdio.h>
using namespace std;
int main(int argc, char ** argv)
{
char str[20] = "AAAAAAAAA";
strcpy(str, "BBBBBB");
cout << str << endl;
return 0;
}
- 以上程序的主要作用是将 “BB…” 复制到 str 字符串中,运行结果如图所示
- 函数运行步骤
- 逆向分析:首先取出传入 strcpy 函数的第一个和第二个参数
- 之后判断第二个参数所指向的字符串是否数据对齐
- 由于 0040121B 并不是 4 的倍数,显然数据没有对齐,从而发生跳转,进行数据对齐操作
注:对第二个参数的字符串是如何进行数据对齐操作的呢?方法简单,每次从第二个参数取出一个字符存入第一个参数指向的字符串的地址,并且第二个字符串指针加一,之后再次判断第二个参数的字符串地址是否数据对齐,如果还没有对齐就循环上述操作,直到数据对齐完毕
- 进行完数据对齐操作之后,将第二个参数所指向的字符串循环每次复制 4 个字节到第一个参数所指向的字符串 + 2 的地址中(因为进行了对齐操作),直到这 4 个字节当中不包含 00 标志(00 表示字符串结尾)
- 由于下面 4 个字节包含 00 字符,所以只是复制了一次
- 最后计算出 00 字符处于 4 个字节的哪一个位置,如图所示处于第 4 个位置,故发生跳转
- 之后把 00 字符串传入 edi 所指向的内存地址
- 寄存器状态和内存分布如图所示:
- strcpy 函数的流程图:
strcmp 函数
- 函数原型:int strcmp(const char *str1, const char *str2)
- 函数功能:把 str1 所指向的字符串和 str2 所指向的字符串进行比较(按字符的 Ascii 码进行比较)
- C/C++ 实现:
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[15];
char str2[15];
int ret;
strcpy(str1, "AAAaaa");
strcpy(str2, "AAaaaa");
ret = strcmp(str1, str2);
if(ret < 0)
{
printf("str1 小于 str2");
}
else if(ret > 0)
{
printf("str1 大于 str2");
}
else
{
printf("str1 等于 str2");
}
return(0);
}
- 以上程序的功能十分简单,比较 str1 和 str2 的大小,并且打印出结果。运行结果如图所示:
- strcmp 函数运行步骤:
- 逆向分析:首先进入 strcmp 函数,之后会取出传入 strcmp 函数的两个参数,也就是 str1 和 str2,取完参数之后判断 str1 字符串是否数据对齐,数据对齐就是 str1 字符串的首地址是否为 4 的倍数,如果 str1 字符串没有数据对齐,那么就会跳转到 0x7571938C 的地方进行数据对齐操作,在数据完成对齐后会 jmp 到 0x7571934E 的地址继续往下执行
- 从寄存器可以看出 ecx 储存的是 str2 字符串的首地址,而 edx 储存的是 str1 字符串的首地址
- 接下来取 str1 字符串的头四个字节放在 eax 中,之后按一个字节的大小分别与 str2 字符串相应的字符做大小比较,如果不相等就发生跳转,需要注意的时,这里比较的是 ascii 码的大小,比如 ‘A’ 字符 ascii 码为 41,而 ‘a’ 字符 ascii 码为 61,这样的话 ‘a’ 就会比 ‘A’ 字符大
- 由于 str1 是 ‘AAAaaa’,而 str2 是 ‘AAaaaa’,所以在第三个字符比较时由于 A != a,故发生了跳转,如果前四个字节都相等的话,会将 str1 和 str2 的指针都向后指 4 个字节,在循环以上过程进行比较
- 值得注意的是如果字符判断相等之后,如果发现 str1 对应的字符为 0(字符串结尾标志),就都会跳转到 0x75719380 这个地址,并且函数返回 0,表示字符串相等
- 比如 str1 为 ‘Aaa’,str2 为 ‘Aaaaaa’,由于 str1 的第四个字符为 0,而前面的字符又和 str2 相等,所以判断 str1 等于 str2;但是如果 str1 为 ‘Aaaa’,而 str2 为 ‘Aaa’ 的话,则 str1 大于 str2,因为进行结尾比较的是 str1 而不是 str2,所以 str1 大于 str2
- 最后根据 CF 标志位,以 sbb 计算的结果作为函数的返回值,如图所示 eax 的值为负数,根据 strcmp 函数的返回值文档可以看出如果函数返回值小于 0,则表示第一个参数字符串小于第二个参数字符串,也就是 str1 小于 str2
- 为什么通过 sbb 操作指令和 CF 标志位能判断出字符串的大小呢,比如 str1 = ‘AAAA’,而 str2 = ‘AAAaaaBB’,由于前三个字符相等所以比较第四个字符,str1 的第四个字符为 A(ascii:41),而 str2 的第四个字符为 a(ascii:61),所以比较的指令就为 cmp 41,61,而 cmp 指令是根据两个操作数相减去影响标志位的,所以 41 - 61 就为负数,导致数据溢出位 CF 变为 1,而 sbb eax,eax 指令就相当于 eax - eax - CF,所以也就能比较字符的大小了
- 那就有人问了,假如大于的话,CF 就为 0 了,岂不是两个字符串相等了吗,其实不会这样的因为在最后使用了 add eax,0x1 的操作,所以只要是大于的话返回值就会为 1,避免了和 0 之间的冲突(shl eax,1 也是同样的道理,为的是避免 add eax,0x1 在负数加一之后和 0 引发冲突)
逆向 memset、strcpy、strcmp 函数到此结束,如有错误,欢迎指正