逆向 ctype.h 函数库 isalnum、iscntrl、islower、isxdigit、tolower 函数
0x01 isalnum
函数
- 函数原型:
int isalnum(int c);
- 函数功能:检查所传的字符是否是字母和数字
- 动态链接库:
ucrtbase.dll
- C\C++ 实现:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
using namespace std;
unsigned int __stdcall re_isalnum(void);
int main(int argc, char** argv)
{
re_isalnum();
return true;
}
unsigned int __stdcall re_isalnum(void)
{
char str[] = "AAAAAAAAAA";
int var1 = 'd';
int var2 = '2';
int var3 = '\t';
int var4 = ' ';
if (isalnum(var1))
{
printf("var1 = |%c| 是字母数字\n", var1);
}
else
{
printf("var1 = |%c| 不是字母数字\n", var1);
}
if (isalnum(var2))
{
printf("var2 = |%c| 是字母数字\n", var2);
}
else
{
printf("var2 = |%c| 不是字母数字\n", var2);
}
if (isalnum(var3))
{
printf("var3 = |%c| 是字母数字\n", var3);
}
else
{
printf("var3 = |%c| 不是字母数字\n", var3);
}
if (isalnum(var4))
{
printf("var4 = |%c| 是字母数字\n", var4);
}
else
{
printf("var4 = |%c| 不是字母数字\n", var4);
}
return true;
}
- 上述程序非常的简单,首先创建了
4
个变量分别为var1、var2、var3、var4
,然后分别判断这 4 个变量是否为数字和字母,并且根据isalnum
函数返回的结果将它们打印出来。打印结果如下图所示:
注:程序中的
str
字符串只是为了定位程序,从而避开mian
函数开始前复杂的初始化过程
- 逆向分析:首先使用
OD
载入被测试的程序,之后搜索字符串str
,使得程序直接定位到isalnum
函数附近处,并且在开头下断点,为的是方便反复调试。如下图所示已经到达了main
函数处,并且看到了isalnum
函数,之后进入第一个 isalnum 函数看看
isalnum
函数的内部有三个小函数,可见这个函数功能不强,但是内部倒是复杂的很
- 由于已经调试过一遍,所以对这几个函数的功能比较了解。第一个函数主要是用于核对该动态链接库地址的一个常量是否等于
0
,等于0
返回0
,不等于0
返回1
setne 命令
是将ZF
标志位去反后储存在al
当中
- 调用完第一个子函数
ucrtbase.63479727
后返回0
,由于返回的是0
,所以跳转会实现,也就跳过了ucrtbase._isalnum_l
函数,直接执行最后一个子函数
- 在进去
ucrtbase.634b9400
函数前,压入了两个参数,分别是64
和107
,64
就是传入需要检测的字符d
(ascii
码为64
)。进去这个函数看看,发现其中又调用了一个孙函数,而且压入的参数是一样的,没办法继续跟进这个孙函数看看
- 这个就是孙函数,流程还是十分简单的。首先取出第一个参数 64,也就是传入需要检测的字符,之后判断
64
是否在-1
到0xFF
之间,为什么要做这个判断呢,因为ascii
码的范围就在-1
到0xFF
之间,所以在开头就进行数据过滤,防止程序出现BUG
。如果超出了这个范围就调用ucrtbase._CrtDbgReportW
函数,这个函数的功能是弹出一个异常对话框表示程序异常,当然了在调试器调试的情况下才会有
- 如果没有超出这个范围的话,就调用
ucrtbase.634b25d7
这个函数,且压入的参数如上图所示,继续跟进这个函数 - 如下图所示:首先和前面一样判断传入检测的字符是否在
-1
到0xFF
之间,如果不在这个范围内的话就调用异常处理函数
- 然后压入传入这个函数的第一个参数
0
,之后调用ucrtbase.63479750
函数,因为调用这个函数并没有传入检测字符的ascii
码64
,而且传入的参数是固定的,所以这个函数和isalnum
函数的功能没有太大的关系,所以直接省略该函数的分析 - 继续向下调试,会调用
ucrtbase.634797e3
函数
ucrtbase.634797e3
函数如下图所示,主要功能是取ecx + 0x4
的地址,ecx
可能是一个对象或者是一个结构体
- 之后经过两次寻址之后,将寻址后的值压入栈中作为第一个参数,并且调用
ucrtbase.63479705
函数,这个函数就是isalnum
当中的一个核心函数,负责检测传入的字符是否为数字或者字母。
F7
进入这个函数,下图就是函数的运行流程,主要是算法- 那么是用什么算法取检测字符是否为数字或者字母的呢,其实很简单,就是运用的
and
命令来判断,并且有一个定值为0x107
,如果一个字符是字母或者数字,通过数组寻址的值和and 107
做运算,那么结果就会大于0
,反之则会等于0
- 而这个数组就隐藏在
eax
当中,在内存中查看一下这个数组:
- 寻址方式就是
movzx eax,word ptr ds:[eax+ecx*2]
这条命令。下面来验证一下,查询一下 Ascii 表,把所有字母和数字的 16 进制都给表示出来,结合数组寻址可以计算出地址如下图所示:
ecx -> ecx*2 -> eax+ecx*2
30-39 -> 60-72 -> 6475BCC8-6475BCDA
41-5a -> 82-B4 -> 6475BCEA-6475BD1C
61-7a -> C2-F4 -> 6475BD2A-6475BD5C
- 也就是图中的这个区域:只要是超过这个区域的值和
107
做and
计算都会等于0
- 实际调试看看:首先进行数组寻址,值储存在
eax
当中,且值为0x82
- 和
0x107
做and
计算后为2
,大于0
,所以字符为数字或者字母
0x02 iscntrl
函数
- 函数原型:
int iscntrl(int c);
- 函数功能:检查所传的字符是否是控制字符。根据标准
ASCII
字符集,控制字符的ASCII
编码介于0x00 (NUL)
和0x1f (US)
之间,以及0x7f (DEL)
,某些平台的特定编译器实现还可以在扩展字符集(0x7f 以上)
中定义额外的控制字符。 - C\C++ 实现:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
using namespace std;
unsigned int __stdcall cx_iscntrl(void);
int main(int argc, char** argv)
{
char str[] = "AAAAAAAAAA";
cx_iscntrl();
return true;
}
unsigned int __stdcall cx_iscntrl(void)
{
int i = 0, j = 0;
char str1[] = "all \a about \t programming";
char str2[] = "Runoob \n tutorials";
/* 输出字符串直到控制字符 \a */
while (!iscntrl(str1[i]))
{
putchar(str1[i]);
i++;
}
/* 输出字符串直到控制字符 \n */
while (!iscntrl(str2[j]))
{
putchar(str2[j]);
j++;
}
return true;
}
- 上述程序是什么意思呢,首先创建了两个字符串,并且分别循环打印字符串中的非控制字符和控制字符,运行结果如下图所示:
- 逆向分析:因为基本上
ctype.h
函数库的所有函数的算法都基本一样,在上面的isalnum
函数中已经详细的分析过了,所以下面就将简略的分析了。首先根据搜索字符串的方式定位到程序的main
函数,理由就不多述了。由于该程序使VS2019
编译的,所以第一个函数的功能是初始化堆栈的,第二个函数才是需要分析的子函数,F7
进入这个NpCxyFW.0040123A
函数
- 在该函数的开头同样的进行了堆栈初始化的操作。值得注意的是有如下这几个命令,这几个命令其实是用来保存
GS Cookie
的,并且异或ebp
加密后储存到local.1
变量的位置,在函数的结尾处会把这个变量取出来解密并且和.data
段中的Cookie
作比较,目的是为了防止栈溢出,是保护栈的一种措施,俗称GS
-> mov eax,dword ptr ds:[0x409004]
xor eax,ebp
mov dword ptr ss:[ebp-0x4],eax
- 继续向下调试后会发现会将两个字符串分别赋值到
local.15
和local.22
局部变量中,之后初始化两个局部变量lcoal.3
和local.6
,且都赋值为0
- 然后会看到一个循环语句,
Ollydbg
已经将循环标注出来了,可以看出local.3
是循环控制变量,再循环结尾处加1
,循环break
出来的条件是eax
不为 0,而eax
是iscntrl
函数的返回值,表示非控制字符的话就会跳出循环。之后通过movsx ecx,byte ptr ss:[ebp+eax-0x3C]
这条语句取出第一个字符串的第一个字符,ebp-3C
为local.15
的地址,eax
为数组寻址,之后调用第一个iscntrl
函数
iscntrl
内部结构如下图所示:上面iscntrl
已经介绍过了,其中第一个子函数是动态链接库中的常量验证操作,验证成功直接跳转,之后进入最后一个子函数ucrtbase.0F2F9400
- 第一个子函数:
ucrtbase.0F2F9400
函数内部继续调用了一个curtbase._chvalidator
函数,且参数和传进来的参数是一样的
- 进去
curtbase._chvalidator
函数看看,首先判断字符是否为在标准的Ascii
码范围内,如果不在的话就转入异常处理。接着会调用curtbase_chvalidator_l
函数
- 在
curtbase_chvalidator_l
函数内部首先依然会对传入的字符进行范围判断,判断是否在Ascii
码的范围内,否的话转入异常处理,和上面一样。ucrtbase.0F2B97E3
这个函数是取数组地址的,在之后的判断字符是否为控制字符会有用
- 直接进入到
ucrtbase.0F2B9705
这个函数,这个函数就是处理字符的核心函数,和isalnum
函数的算法是一模一样的,且and
计算的固定值变为了0x20
- 这个就是
eax
中储存的数组的首地址
0x03 islower
函数
- 函数原型:
int islower(int c);
- 函数功能:检查所传的字符是否是小写字母
- C\C++ 实现:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
using namespace std;
unsigned int __stdcall cx_islower(void);
int main(int argc, char** argv)
{
char str[] = "AAAAAAAAAA";
cx_islower();
return true;
}
unsigned int __stdcall cx_islower(void)
{
int var1 = 'Q';
int var2 = 'q';
int var3 = '3';
if (islower(var1))
{
printf("var1 = |%c| 是小写字母\n", var1);
}
else
{
printf("var1 = |%c| 不是小写字母\n", var1);
}
if (islower(var2))
{
printf("var2 = |%c| 是小写字母\n", var2);
}
else
{
printf("var2 = |%c| 不是小写字母\n", var2);
}
if (islower(var3))
{
printf("var3 = |%c| 是小写字母\n", var3);
}
else
{
printf("var3 = |%c| 不是小写字母\n", var3);
}
return true;
}
- 这个程序的首先初始化
3
个局部变量,之后分别对它们进行判断,判断是否为小写字母,并且根据islower
函数的返回值打印出结果。运行结果如下图所示:
- 逆向分析:因为流程和上面基本一样,所以直接贴出核心函数,此时
and
计算的定值为02
islower
函数的功能是判断是否为小写字母,在Ascii
码中小写字母的范围为0x61
到0x7A
,根据movzx eax,word ptr ds:[eax+ecx*2]
数组寻址命令,可以计算出范围是0xFF5BD2A ~ 0xFF5BD5C
0x04 isxdigit
函数
- 函数原型:int isxdigit(int c);
- 函数功能:检查所传的字符是否是十六进制数字
- C\C++ 实现:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
using namespace std;
unsigned int __stdcall cx_isxdigit(void);
int main(int argc, char** argv)
{
char str[] = "AAAAAAAAAA";
cx_isxdigit();
return true;
}
unsigned int __stdcall cx_isxdigit(void)
{
char var1[] = "tuts";
char var2[] = "0xE";
if (isxdigit(var1[0]))
{
printf("var1 = |%s| 是十六进制数字\n", var1);
}
else
{
printf("var1 = |%s| 不是十六进制数字\n", var1);
}
if (isxdigit(var2[0]))
{
printf("var2 = |%s| 是十六进制数字\n", var2);
}
else
{
printf("var2 = |%s| 不是十六进制数字\n", var2);
}
return true;
}
- 运行结果如下图所示:
- 逆向分析:核心算法函数如下图所示,
and
计算的固定值为0x80
- 在
Ascii
码中,16
进制的范围为0x30 ~ 0x39 && 0x41 ~ 0x46 && 0x61 ~ 0x66
,数组首地址储存在eax
当中,根据movzx eax,word ptr ds:[eax+ecx*2]
这条数组寻址命令,可以计算出范围在0xF73BCC8 ~ 0xF73BCDA && 0xF73BCEA ~ 0xF73BCF4 && 0xF73BD2A ~ 0xF73BD34
。数组内存数据如下图所示:
0x05 tolower
函数
- 函数原型:
int tolower(int c);
- 函数功能: 把给定的字母转换为小写字母
- C\C++ 实现:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
using namespace std;
unsigned int __stdcall cx_tolower(void);
int main(int argc, char** argv)
{
char str[] = "AAAAAAAAAA";
cx_tolower();
return true;
}
unsigned int __stdcall cx_tolower(void)
{
int i = 0;
char c;
char str[] = "RUNOOB";
while (str[i])
{
putchar(tolower(str[i]));
i++;
}
return true;
}
- 上述程序的功能是将
str
字符串全部字符都转换成小写字符,运行结果如下图所示:
- 逆向分析:根据字符串直接定位到
main
函数的起始部分,然后进行完堆栈的初始化操作后,将需要测试的字符串复制到local.3
局部变量中
- 紧接着的是一个循环(
Ollydbg
中已经标识出来了),local.3
变量初始化为0
,用于循环计数。之后使用movsx ecx,byte ptr ss:[ebp+eax-0x28]
命令进行数组寻址,循环取出字符串中的每一个字符,在这里取出的是第一个字符,接着调用tolower
函数
F7
进入tolower
函数,发现其会调用3
个子函数。第一个子函数是进行动态链接库中常量的验证工作,和tolower
函数的核心功能没什么太大的联系。验证过后直接跳转到0x0F8C5E67
这个地址- 下面就是实现小写字符转换的核心语句,首先程序会判断传入的字符是否在
0x41 ~ 0x5A
这个范围内,其实就是判断是否为大写字母,如果不是大写字母就返回传入的字符;反之如果是大写字母的话,就加上0x20
使之变为小写字母,之后返回转换完的小写字母,函数结束
总结
- 总的来说
ctype.h
函数库的实现难度比较高,尤其是这么多函数通过一个数组来判断字符,其算法是十分精妙的,可见开发这个库的程序员功力满深的,唯一美中不足的是函数的调用问题,函数调用有重复,而且对同一字符进行了3
次重复的验证,降低了效率…
逆向
ctype.h
库的isalnum、iscntrl、islower、isxdigit、tolower
函数到此结束,如有错误,欢迎指正
逆向 ctype.h 函数库 isalnum、iscntrl、islower、isxdigit、tolower 函数的更多相关文章
- 逆向 string.h 函数库 memset、strcpy、strcmp 函数
memset 函数 函数原型:void *memset(void *str, int c, size_t n) 主要功能:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符 ...
- 【C++实现python字符串函数库】二:字符串匹配函数startswith与endswith
[C++实现python字符串函数库]字符串匹配函数startswith与endswith 这两个函数用于匹配字符串的开头或末尾,判断是否包含另一个字符串,它们返回bool值.startswith() ...
- 逆向 string.h 函数库 strlen、memchr、strcat 函数
strlen 函数 主要功能:返回字符串的长度 C/C++ 实现: #include <iostream> #include <stdio.h> #include <st ...
- c 头文件<ctype.h>(一)
头文件<ctype.h>中声明了一些测试字符的函数. 每个函数的参数均为int类型,参数的值必须是EOF或可用unsigned char类型表示的字符,函数返回值为int类型. 如果参数c ...
- gcc 头文件是用户应用程序和函数库之间的桥梁和纽带 功能的真正逻辑实现是以硬件层为基础
gcc GCC, the GNU Compiler Collection - GNU Project - Free Software Foundation (FSF) http://gcc.gnu.o ...
- 标准C函数库的使用方法
本篇介绍若干经常使用的标准C函数的使用方法,主要介绍stdio(标准输入输出).math(数字函数库).time(时间函数库).stdlib(标准函数库)string(标准字符串函数)等. 最后更新 ...
- (转)函数库调用 VS 系统调用
Linux下对文件操作有两种方式:系统调用(system call)和库函数调用(Library functions).可以参考<Linux程序设计>(英文原版为<Beginning ...
- python函数库及函数标准库
一.系统库提供的内部函数 字符函数库: 1)str.islower() :字符串是否全部是小写 2)str.isspace() :字符串是否为空 3)help(str):查询字符串函数库 4)str. ...
- 为开发者准备的 Android 函数库(2016 年版)
转载:http://www.androidchina.net/5922.html第三方函数库(译者注:包括第三方提供的 SDK,开源函数库)以惊人的方式助力着 Android 开发,借助这些其他开发人 ...
随机推荐
- 漏洞复现-CVE-2016-4977-Spring远程代码执行
0x00 实验环境 攻击机:Win 10 靶机也可作为攻击机:Ubuntu18 (docker搭建的vulhub靶场)(兼顾反弹shell的攻击机) 0x01 影响版本 Spring Secu ...
- java异常的 理解
1.体系结构 java.lang.Object |----java.lang.Throwable |-------java.lang.Error:错误,java程序对此无能为力,不显式的处理 |--- ...
- PTE 准备之 Repeat sentence
Repeat sentence After listening to a sentence ,repeat the sentence 3-9 seconds 15 seconds Strategies ...
- python之commands和subprocess入门介绍(可执行shell命令的模块)
一.commands模块 1.介绍 当我们使用Python进行编码的时候,但是又想运行一些shell命令,去创建文件夹.移动文件等等操作时,我们可以使用一些Python库去执行shell命令. com ...
- 一文读懂MySql主从复制机制
作为一个关系型数据库,MySQL内建地提供数据复制机制,这使得在使用时,可以基于其复制机制实现高可用架构等高级特性,从而使得MySQL无需借助额外的插件或其他工具就具备适用于生产环境.这是MySQL得 ...
- TCP 和 UDP 协议简介
一.TCP TCP(Transmission Control Protocol),传输控制协议,对"传输.发送.通信"进行"控制"的协议,它充分地实现了数据传输 ...
- OO 第三单元
一.JML语言理论基础 JML 是用于对 Java 程序进行规格化设计的一种表示语言,为严格的程序设计提供了一套行之有效的方法. 我个人对于 JML 的几点看法: JML 的规格化设计相较于自然语言的 ...
- 基于gitlab的项目管理流程
框架 背景 个人是不太愿意使用用户体验差的软件来做项目管理,行业内,要找到这么一款软件,又要符合自己的需求,着实不容易.要免费,易用性要好,要安全,要有数据统计.而程序员的世界,SVN 之后,可能没有 ...
- Java(114-132)【Scanner类、Random类、ArrayList类】
1.API概述和使用步骤 应用程序编程接口.Java的API是一本程序员的字典,学会查询 2.Scanner 概述及其API文档 键盘输入 类都是大写的Scanner,关键字是小写的public 3. ...
- 原创 Spring Boot 2.3 新特性分层JAR
背景 在我们实际生产容器化部署过程中,往往会遇到 Docker 镜像很大,部署发布很慢的情况 影响 docker 镜像大小的因素,主要有以下三个方面: 基础镜像的大小 .尽量选择 aphine 作为基 ...