逆向 string.h 函数库 strlen、memchr、strcat 函数
strlen 函数
- 主要功能:返回字符串的长度
- C/C++ 实现:
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
int main(int argc, char **agrv)
{
// OD 字符串查找,便于定位 main 函数
char a[20] = "AAAAAAAAAAAAAA";
const char str[] = "http://www.runoob.com";
int ret = strlen(str);
cout << "字符串的长度为: " << ret << endl;
return(0);
}
- strlen 函数运行结果:

- 逆向分析:将程序载入 OD 后,查找 ‘AAAAA…’ 字符串的位置(由于程序是由 VS2017 编写,所以 main 函数初始化较为复杂,直接跳过即可),下图中的 NpCxyFw.00411091 函数就是 strlen 函数,push eax 压入的是需要计算大小的字符串 str,F7 进入 strlen 函数

- 由于这个函数比较简单,所以代码量不是很大:

- 流程图如下图所示:

- 主要算法:字符串对齐算法 + 判断字符串结束算法

- 字符串对齐算法:该算法的主要目的是判断字符串是否对于 CPU 对齐,也就是说是否字符串的开头位置是否对于 DWORD 字节对齐。首先会使用 test ecx,3 对字符串的头地址进行按位与操作,判断是否进行了数据对齐,如果数据对齐跳转就会实现


注:为什么使用 test ecx,3 能判断数据是否对齐呢,假如字符串的首地址是 0019FEC4,那么二进制表示就为 110011111111011000100,而 3 的二进制是 011,那么最后两位就不为 0,表示数据已经对齐:
那么如果字符串的首地址是 0019FEC3,二进制表达为 110011111111011000011,这样的话数据就不会对齐:
- 假如跳转没有实现,就循环的将 ecx 中储存的字符串头指针往右移动,每次移动一个字节,直到数据对齐为止。值得注意的是期间会使用 test al,al 判断 al 是否为 0,表示字符串是否到达结尾

- 判断字符串结束算法:这个算法就比较有趣了,主要分为两个步骤,首先在数据对齐后会跳转到 0x008F6E0,之后从头开始循环取出 4 个字节大小数据存放在 eax 中,那么字符串 str 就被分成了 6 部分
http ://w ww.r unoo b.co m,之后通过 (eax + 7EFEFEFF) ^ (eax ^ -1) 计算出的结果与 81010100 做 test 判断,判断指定的位是否为 0 - 为什么要这么做呢,意义主要是判断字符是否含有字符串结尾标志 00,如 str 字符串所示最后一个字符是 m,但由于字符串的结尾结束符是 00,所以最后取出 4 个字节就包含 00 结束标志


- 由第一个步骤可以判断出字符串的结尾处在哪里,可是这个是 4 个字节大小的判断,并没有精确到字节单位,所以第二个步骤判断字符串结尾标志 00 在 4 个字节的哪个位置(test al、ah FF0000、FF000000),并把此位置的字符串指针储存在 eax 中,表示字符串结尾指针(注意不包含 00 结尾标志)
- 最后用字符串结尾指针 0019FED9 减去开头指针 0019FEC4 就得出了字符串的长度 21,ecx 中储存的是字符串开头指针

memchr 函数
- 主要功能:返回指定字符在字符串中的位置
- 参数:(1) 传入字符串 (2) 传入指定字符 (3) 字符串的长度
- C/C++ 实现:
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
int main(int argc, char **agrv)
{
char a[20] = "AAAAAAAAAAAAAA";
const char str[] = "http://www.baidu.com";
const char ch = '.';
char *ret = (char*)memchr(str, ch, strlen(str));
cout << ret << endl;
return(0);
}
- memchr 函数运行结果:

- 逆向分析:首先取出字符串的长度(第三个参数)存放在 eax 中,并且判断字符串的长度是否为 0,是的话函数直接返回 0;之后取出字符串的首地址(第二个参数)存放在 edx, 然后将指定字符(第二个参数)存放在清空的 ebx 中

- 参数取完之后,判断字符串的首地址是否处于对齐状态,如果处于非对齐状态,需要进行数据对齐操作,值得注意的是在数据循环对齐期间如果碰到字符串长度为 1 则直接返回

- 接下来对判断字符串的长度是否小于 4 个字节,如果小于 4 个字节直接返回

- 之后将指定字符(第二个参数),填充满 ebx,为下面的异或操作做基础


- 这一步的操作是每隔 4 个字节读取一次字符串,读取的内容会储存在 ecx 中,之后将 ecx 带入这个表达式中 ecx = (ecx ^ FFFFFFFF) ^ (2E2E + 7EFEFEFF + (ecx ^ 2E2E2E2E)),并且和 81010100 做异或操作。这一步主要是判断每 4 个字节中是否有指定的字符 ‘.’

- 循环之后,判断刚才存在指定字符的 4 个字节中指定字符的位置,如果字符是 4 个字节中的第 2 个那么就将指针减去 3 个字节,这样就刚好指在了指定字符的位置

- 最后函数返回字符串中第一个指定字符的指针,所以函数打印出了指定字符 ‘.’ 及以后的字符

- 总结 memchr 函数的运行流程图:


strcat 函数
- 函数原型:char *strcat(char *dest, const char *src)
- 函数功能: 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
- C/C++ 实现:
#include <iostream>
#include <String.h>
int main(int argc, char **argv)
{
char str1[] = "WHO ";
char str2[] = "AM ";
char str3[] = "I";
strcat(str1, str2);
strcat(str1, str3);
cout << str1 << endl;
return 0;
}
- 以上程序的运行结果如图所示:

- 函数运行步骤:

- 逆向分析:首先取出传入 strcat 函数的参数1中的字符串地址,存放在 ecx 中,之后判断 ecx 的值是否数据对齐,如果数据没有对齐,则对数据进行对齐操作

注:程序中对数据对齐的判断是指数据存放的地址是否为 4 的倍数
- 之后会以一个 DWORD 类型为大小循环判断参数一是否包含字符串结尾标志 00

- 由于 "WHO " 只有 4 个字节,所以循环第二次才包含 00 字符,接下来就需要计算 00 字符串的位置了,由于每次是以一个 DWORD 类型为大小(4 个字节),所以 00 字符处于 eax 的低位,故经过 al 比较之后发生了跳转。这个步骤主要的作用是将字符串指针指向该字符串的结尾,也就是说指向 "WHO " 的结尾,为的是方便将第二个参数字符串追加到第一个字符串的结尾

- 然后取出传入 strcat 函数的第二个参数,用于追加到第一个参数字符串的结尾。和上面处理第一个字符串的方法类似,首先对第二个参数字符串地址进行数据对齐判断,如果不对齐就对数据进行对齐

注:值得注意的是,参数二字符串的对齐操作有一点特殊,该对齐操作每次一个字节的循环将参数二字符串添加到参数一字符串的结尾,并且将参数二字符串的指针 + 1,之后再次判断数据是否对齐,如果没有再次循环
- 最后在满足没有字符串结尾标志 00 的情况下,每次 4 个字节将参数二字符串循环复制参数一字符串的结尾,当 4 个字节中包含 00 结尾时就计算 00 的位置,以便完成接下来的复制

逆向 strlen、memchr、strcat 函数到此结束,如有错误,欢迎指正
逆向 string.h 函数库 strlen、memchr、strcat 函数的更多相关文章
- numpy函数库中一些常用函数的记录
##numpy函数库中一些常用函数的记录 最近才开始接触Python,python中为我们提供了大量的库,不太熟悉,因此在<机器学习实战>的学习中,对遇到的一些函数的用法进行记录. (1) ...
- 逆向 string.h 函数库 memset、strcpy、strcmp 函数
memset 函数 函数原型:void *memset(void *str, int c, size_t n) 主要功能:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符 ...
- 逆向 time.h 函数库 time、gmtime 函数
0x01 time 函数 函数原型:time_t time(time_t *t) 函数功能:返回自纪元 Epoch(1970-01-01 00:00:00 UTC)起经过的时间,以秒为单位.如果 se ...
- C语言中的string.h中的内存字符串处理函数
转载请注明出处:http://blog.csdn.net/zhubin215130/article/details/8993403 void *memcpy(void *dest, const voi ...
- 使用c函数库的两个函数strtok, strncpy遇到的问题记录
1. strtok 问题背景: 解析形如 “1,2,3,4,5”字符串到整数数组 (1)计算个数 char* delim = ","; int count = 0; int *nu ...
- 字符串操作函数<string.h>相关函数strcpy,strcat,等源码。
首先说一下源码到底在哪里找. 我们在文件中包含<cstring>时,如果点击右键打开文档, 会打开cstring,我们会发现路径为: D:\Program Files\visual stu ...
- Linux C函数库参考手册
目录 第1章 字符测试函数 isalnum(测试字符是否为英文字母或数字) isalpha(测试字符是否为英文字母) isascii(测试字符是否为ascii码字符) isblank(测试字符是否为空 ...
- Linux C函数库大全
(1)字符测试函数 isalnum(测试字符是否为英文字母或数字) isalpha(测试字符是否为英文字母) isascii(测试字符是否为ASCII码字符) isblank(测试字符是否为空格字符) ...
- Lua 的函数库 01
这里只介绍和插件编写比较有关的几个函数. 详细的Lua手册请参照Lua Reference Manual 5.1. table函数库 一部分的table函数只对其数组部分产生影响, 而另一部分则对整个 ...
随机推荐
- Asp.Net Core WebAPI中启用XML格式数据支持
因为XML是一种非常常用的数据格式,所以Asp.Net core提供了非常便利的方式来添加对XML格式的支持 只需要在IOC注册Controller服务的后面跟上.AddXmlDataContract ...
- rman全备脚本
cat rman_back.sh #!/bin/bash source /home/oracle/.bash_profile rman log=/u01/backup/backupall_rman ...
- 有必要了解的大数据知识(二) Hadoop
前言 接上文,复习整理大数据相关知识点,这章节从MapReduce开始... MapReduce介绍 MapReduce思想在生活中处处可见.或多或少都曾接触过这种思想.MapReduce的思想核心是 ...
- Dotnet洋葱架构实践
一个很清晰的架构实践,同时刨刨MySQL的坑. 一.洋葱架构简介 洋葱架构出来的其实有一点年头了.大约在2017年下半年,就有相关的说法了.不过,大量的文章在于理论性的讨论,而我们今天会用一个项目 ...
- 一种3位sar adc工作过程推导(二)
3位sar adc采用下图的电容阵列,需要23个电容,它的基本单元有二进制加权的电容阵列.1个与LSB电容等值的电容:它利用电容上的初始电荷再分配完成二进制搜索算法,因此功耗一般比较小,而且不需要额外 ...
- JAVA面试-计算机网络-TCP三次握手
学习原因 这个是面试的一个常问热点,所以务必要掌握. 通俗示例 小红是人事部门的员工,现在正在招收IT人员,小明看到招聘信息和待遇,感觉很适合自己,所以准备和小红发消息了解具体情况.而简历在本故事中代 ...
- Android 在活动中使用 Menu
•前行必备--创建 menu 首先,新建一个项目,选择 Empty Activity 选项,并命名为 Test Menu: 这样 Android Studio 自动为我们新建 MainActivity ...
- 什么是事务?事务的四个特性(ACID)?并发事务带来哪些问题?事务隔离级别都有哪些?事务的传播特性
什么是事务? 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事物的四个 ...
- vue-i18n 国际化语言切换
vue-i18n 用于前端vue项目中,需要多语言切换的场景 安装方法(npm) npm install vue-i18n 简单使用 1.在vue项目的main.ts文件中实例化 i18n imp ...
- 前端 | JS Promise:axios 请求结果后面的 .then() 是什么意思?
Promise 是JS中一种处理异步操作的机制,在现在的前端代码中使用频率很高.Promise 这个词可能有点眼生,但你肯定见过 axios.get(...).then(res => {...} ...

