逆向 time.h 函数库 time、gmtime 函数
0x01 time 函数
- 函数原型:
time_t time(time_t *t)
- 函数功能:返回自纪元
Epoch(1970-01-01 00:00:00 UTC)
起经过的时间,以秒为单位。如果seconds
不为空,则返回值也存储在变量seconds
中 - C\C++ 实现:
#include <stdio.h>
#include <time.h>
int main ()
{
time_t seconds;
seconds = time(NULL);
printf("自 1970-01-01 起的小时数 = %ld\n", seconds/3600);
return(0);
}
- 上述程序的功能是通过
time
函数获取自1970-01-01 00:00:00
后经过的时间,之后打印出经过的小时数,程序的运行结果如下图所示:表示自1970-01-01 00:00:00
之后经过了432971
个小时
- 逆向分析:首先进入
main
函数,由于time
函数传入的参数为NULL
,所以将0
压入栈之后调用time
函数
- 进入函数后进行栈顶和栈底的操作,之后直接通过
jmp
跳转到msvcrt._time32
的地址,然后继续向下调试
- 单步到这个位置可以发现在
time
函数中直接调用了GetSystemTimeAsFileTime()
这个 API 函数,这个函数属于底层函数,是操作系统直接提供的接口函数
- 看一下微软文档中给出的定义,从函数功能上可以看出这个
API
函数可以实时的获取系统时间,且获取到的时间是UTC
格式。从参数上来看,传入的参数是一个指向FILETIME
结构体的指针
- 再来看一下
FILETIME
结构体,有两个数据成员都是DWORD
格式(4
个字节),dwLowDateTime
表示低位时间,而dwHighDateTime
表示高位的时间,关于高位时间和低位时间的区别会在下面说到,值得注意的是时间的单位是100
纳秒
- 再来看一下调用
GetSystemTimeAsFileTime
API 函数的例子,lea eax,[local]
这个命令是取函数中第二个局部变量的地址并且存放到eax
当中,再将eax
压入栈中之后调用函数,结合上面GetSystemTimeAsFileTime
函数的文档的分析可以知道eax
其实就是FILETIME
结构体
- 调用完
GetSystemTimeAsFileTime
函数之后,会将FILETIME
结构体的dwLowDateTime
储存在ecx
当中,将dwHighDateTime
储存在eax
当中
- 还记得上面的文档吗 ?
GetSystemTimeAsFileTime
函数返回的时间格式是UTC
时间格式,且是从1601-01-01
开始计时的,单位为100
纳秒,而time
函数返回的时间则是从1970-01-01
开始计时的,单位为秒,所以下面会进行UTC
格式的时间转换。首先会将时间的高位加上0xfe624e21
,低位加上2AC18000
,这一步的目的就是将1601-01-01
调整到1970-01-01
- 以高位为例子,调整前为
0x01d5122f
而调整后为0x00376050
,用前减去后结果为0x19db1df
- 转换为
10
进制
- 由于是以
100
纳秒为单位,所以乘以100
得出为369
年,而1970
减去1601
刚刚为369
年
- 时间转换之后,调用如下函数,这个函数的作用是将纳秒转换为秒
- 进入这个函数看一下,首先取出第四个参数判断是否为
0
,之后取出第三个参数10000000
,然后将低位和高位的时间分别处以100000000
即可转换为秒单位
注:
1
秒等于十亿纳秒,而上述时间单位为100
纳秒,所以转换为秒只需要除以1
千万即可
- 最后返回自
1970
年以来的秒数
- 在
time
函数的最后会判断传入的参数是否为0
,如果不为0
,则将结果放入传入的变量内
0x02 gmtime
函数
- 函数原型:
struct tm *gmtime(const time_t *timer)
- 函数功能:C 库函数
struct tm *gmtime(const time_t *timer)
使用time
函数返回的值来填充tm
结构,并用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示 - C\C++ 实现:
#include <stdio.h>
#include <time.h>
#define BST (+1)
#define CCT (+8)
int main ()
{
time_t rawtime;
struct tm *info;
time(&rawtime);
/* 获取 GMT 时间 */
info = gmtime(&rawtime);
printf("当前的世界时钟:\n");
printf("伦敦:%2d:%02d\n", (info->tm_hour+BST)%24, info->tm_min);
printf("中国:%2d:%02d\n", (info->tm_hour+CCT)%24, info->tm_min);
return(0);
}
- 上述程序的作用主要是获取由
time
函数返回的时间(从1970.1.1
开始的小时数),之后放入gmtime
函数转换成更为详细的时间单位。tm
结构体如下图所示,这个就是更为精确的时间细分:
struct tm {
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月份,范围从 0 到 11 */
int tm_year; /* 自 1900 起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
- 程序运行的步骤:(1) 获取纤程局部储存
fls
(2)申请堆空间储存tm
结构体(3)对传入的time
返回的参数开始转换,转换的结果放入tm
结构体当中(4)返回tm
结构体的指针,函数调用结束 - 运行结果如下图所示:
- 下面开始逆向分析,由于计算机处理数据和人的计算方式有很大的不同,所以逆向其中的算法还是比较爽的。首先找到
main
函数的入口点,这里用的是Cfree-5
编译所以main
入口点比较好找,如果是微软的VS
编译的话就需要别的方法了,因为在 main 函数之前的初始化工作太复杂了。在main
函数的入口处可以很清楚的看到首先调用了time
函数,返回值储存在eax
当中,之后通过push eax
将其压入栈中,然后调用gmtime
函数,最后调用打印函数printf
- 查询一下
eax
中的值为0x240FF1C
- 由于传入的是一个地址,所以根据
eax
查询其指向的地址,可以发现值为0x5CEA203A
,需要注意的是单位是秒,为什么是倒过来读呢,因为time
函数返回的是数字类型,所以是以小尾的方式储存在内存空间中,其大小为4
个字节
- 将
0x5CEA203A
转换成年单位,得到49.4307314
年,刚刚说了这个时间是从1970-01-01
开始算的,以年为单位加上49
的结果刚好是2019
年 - 接下来
F7
进入gmtime
函数看看,开始的时候主要操作栈顶和栈底,这个对分析函数没什么用处,直接跳转到mscrt._gmtime32
即可
- 跳转过后发现会调用两个子函数,逆向之后发现第一个函数主要功能是获取纤程局部储存
FLS
,并且申请堆空间用于存放tm
结构体;而第二个函数则是核心函数,主要负责时间的转换
- 首先看一下第一个函数把,由于功能比较简单就不单步调试了。如图和注释所示,有两个子函数,第一个是获取纤程局部储存
FLS
,而第二个函数是申请堆空间,而msvcrt,_error
函数主要用作错误处理
注:调用一些比较复杂的系统
API
函数需要非常小心,因为容易出错,所以error
函数用的非常多。但是需要注意的是error
的使用要注意多线程问题,防止多个线程对用一个error
变量进行争抢
- 下面就是获取纤程局部储存的函数,其中调用了系统
API
函数FlsGetValue
,并且使用GetLastError
函数设置错误信息。当时逆向的时候也是查阅了很多的资料但没有FLS
和FlsGetValue
的资料可供了解,所以尚不清楚这个调用这个函数的目在哪里。
- 由于
FlsGetValue
调用成功了,所以直接跳转到如下位置,之后通过SetLastError
设置错误码为最后一次获取到的错误码,也就是刚刚GetLastError
函数获取到的错误码,最后返回FlsGetValue
的返回值,也就是获取到的局部储存FLS
的地址
- 调用完获取纤程局部储存的函数,之后看看获取堆空间的函数,可以看出调用这个函数只有一个参数
0x24
,应该是申请堆空间的大小
- 进入申请堆空间函数,从函数的运行流程可以大致的得出这个函数主要是通过循环的方式调用
malloc
申请堆空间,申请堆空间的大小就是传入的第一个参数0x24
,如果malloc
调用失败的话就通过sleep
函数隔段时间后再次调用,直到超出了某些限制值。如果malloc
调用成功,那么该函数则返回申请堆空间的首地址
- 运行完申请堆空间的函数后将堆空间的首地址储存在
FLS
偏移44
个字节的地方,之后再次返回堆空间的首地址
- 这样一来第一个函数就分析完了,下面来到第二个函数,这个函数就是转换时间的核心函数。从图中可以看出,这个函数传入了两个参数,第一个参数是申请的堆空间的首地址,第二个参数是
time
函数返回的时间,两个参数的作用就不再多述了
F7
进入这个函数,开始单步调试。首先从参数中取出堆空间的首地址,之后判断是否申请成功,如果申请成功的话就跳过设置错误信息的步骤
- 然后初始化堆空间,其实就是将堆空间覆盖为
FFFF...
,成功之后再次跳转,目的是忽略设置异常的步骤
- 之后从参数中取出
time
函数的返回值,并且和0xFFFF5740
做比较,说明该时间不能大于136
年,成功之后再次跳转
- 还记得
tm
结构体吗,首先做的转换就是将time
函数返回的秒数转换成年,具体算法:(1)通过0x5CEA203A / 0x7861F80
得到多少年,且余数edx
约在1 - 4
年之间(2)使用5CEA203A / 7861F80 * F879E080 + 5CEA203A
公式计算出余数(3)根据余数加上固定的年数得到一共多少年,如果是1.3
年就加上1
年;2.4
年就加上2
年 - 机器的
CPU
计算方法和人的计算方法有很大的不同,最大的难点就是为何使用5CEA203A / 7861F80 * F879E080 + 5CEA203A
公式去计算余数,直接取出余数不行吗
- 来分析一下
5CEA203A / 7861F80 * F879E080 + 5CEA203A
取余公式:
原式等于: 5CEA203A / 7861F80 * F879E080 + 5CEA203A
= C * F879E080 + 5CEA203A
= BA5B68600 + 5CEA203A
= A5B68600 + 5CEA203A
= 102A0A63A
= 02A0A63A
- 可能有点难理解,转换一下就行了:
原式等于: 原数 / 7861F80 * F879E080 + 原数
= 商 * F879E080 + 原数
= BA5B68600 + 原数
= A5B68600 + 原数
= 102A0A63A
= 余数
- 之后还需要考虑到溢出:
原式等于: 原数 / 7861F80 * F879E080 + 原数 - B00000000 - 100000000
= 商 * F879E080 + 原数- B00000000 - 100000000
= BA5B68600 + 原数 - B00000000 - 100000000
= A5B68600 + 原数 - 100000000
= 102A0A63A - 100000000
= 02A0A63A
= 余数
- 而人的计算方式是这样的:
原数 / 7861F80 = 商 ... 余数 => 余数 = 原数 - 商 * 7861F80
- 所以将上面的式子化简之后,和
商 * 7861F80 + 余数 = 原数
其实是一样的:
原数 / 7861F80 * F879E080 + 原数 - B00000000 - 100000000 = 余数
原数 / 7861F80 * F879E080 + 原数 - C00000000 = 余数
C * F879E080 + 原数 - C00000000 = 余数
C * (F879E080 - 100000000) + 原数 = 余数
C * -7861F80 + 原数 = 余数
原数 - 商 * 7861F80 = 余数
eax
当中储存的就是年数
- 之后将
eax
存入tm
结构体偏移0x14
的位置,也就是int tm_year
在结构体tm
中的位置,其中ebx
指向的就是tm
结构体的地址,而且用的是类似数组的寻址
- 既然知道了
ebx
是tm
结构体的地址,那么下面逆向起来就快了,因为理解时间格式便于逆向其中的算法。完成了年的转换之后接下来根据tm
结构体成员变量的位置就可以推出下面转换的是天数,首先将0x15180
放入ecx
中,接着将余下的年数除以0x15180
得出一年当中的第几天(余下的年数就是上面转换年数的余数,以秒表示),将商存入tm
结构体偏移0x1C
的位置,余数存入esi
中
0x15180
十进制表示为86400
秒,刚好为1
天
- 接下来转换月份,就是处于一年当中的第几个月,范围是
0 - 11
,0
表示1
月,怎么转换的呢:通过循环比较ecx + 4
地址往后的值进行比较,如果大于就跳转。最后将月份储存在tm
结构体偏移0x10
的地方
ecx + 4
之后的值其实就是月份叠加起来的值,比如1
月就是1E(30天)
,2 月就是3A(58天=1月+2月)
,edi
中记录着月份的值,且每次循环加1
。那为什么1
月是30
天,2
月是58
天,怎么都少了一天呢,因为edi
初始值就为1
,所以是在1
月的基础上加的
- 然后转换的是一月当中的第几天,这个比较简单,只需要将一年当中第几天减去
ecx + 4
的数组中表示的最大月数即可,计算结果为1A(26天)
。结果储存在tm
结构体偏移0xC
的位置
- 完成了一月当中的第几天的转换后,下面转换的是一周当中的第几天,算法很简单:首先取出
time
函数返回的秒数,之后除以0x15180(1天)
得到 1 年当中第几天,之后再除以7
,余数edx
就是一周当中第几天。结果储存在tm
结构体偏移0x18
的位置
- 最后就是转换时分秒了,由于算法比较简单就统一说了:(1)小时的转换是用上面余下的天数除以
0x1E0(3600秒)
(2)分的转换是使用余下的小时数除以3C(60秒)
(3)秒的转化就是余下的秒数,这个不需要计算(4)最后将这三个值分别存入tm
结构体偏移0x8、0x4、0x0
的地方
- 最后返回堆空间的首地址,也就是
tm
结构体的地址。如图所示tm
结构体的所有变量都已经被覆盖成转换后的值。需要注意的是返回值通过esi
返回,而不是一般的eax
逆向
time
、gmtime
函数到此结束,如有错误,欢迎指正
逆向 time.h 函数库 time、gmtime 函数的更多相关文章
- discuz核心函数库function_core的函数注释
/** * 系统错误处理 * @param <type> $message 错误信息 * @param <type> $show 是否显示信息 * @param <typ ...
- 苹果浏览器Safari对JS函数库中newDate()函数中的参数的解析中不支持形如“2020-01-01”形式
苹果浏览器safari对new Date('1937-01-01')不支持,用.replace(/-/g, "/")函数替换掉中划线即可 如果不做处理,会报错:invalid da ...
- 逆向 ctype.h 函数库 isalnum、iscntrl、islower、isxdigit、tolower 函数
0x01 isalnum 函数 函数原型:int isalnum(int c); 函数功能:检查所传的字符是否是字母和数字 动态链接库:ucrtbase.dll C\C++ 实现: #define _ ...
- 【C语言入门教程】5.6 函数库和文件
函数库是为代码复用建立的,将同一类型,需要在不同的程序里使用的函数放置在一起,就组成了一个函数库.如 C 语言的标准库,它集合了开发者常用的函数.开发者自行编写的函数也可以组成函数库,通常称之为自定义 ...
- java类库 java API jar包 C语言函数库
翁凯说:java的强大是因为类库的强大 C/C++强大是因为函数库 在程序中用到系统提供的标准函数库中的输入输出函数时 应在程序的开头写上一行:#include"stdio.h"或 ...
- PHP用mb_string函数库处理与windows相关中文字符
昨天想批处理以前下载的一堆文件,把文件里的关键内容用正则匹配出来,集中处理.在操作文件时遇到一个问题,就是windows操作系统中的编码问题. 我们都知道windows中(当然是中文版),文件名和文件 ...
- 自定义JSTL标签和函数库
一.自定义JSTL标签 1.编写标签处理类: (1)实现 SimpleTag 接口,通过 setJspContext()方法可以获取到 jspContext 对象,实际上也是 pageContext ...
- 人生维艰,何不利用开源.NET函数库让工作更轻松
今天推荐的文章会谈到一些让你工作更轻松的开源.NET函数库. 即使业界有时候认为.NET开源社区不太健康,很多开发团队都更多依赖于微软提供的东西来开发.不过最近在.NET世界中还是诞生了一些优秀和有意 ...
- JSFunction-Javascript常用函数库
最近正在整理书写常用的Javascript函数库,此函数库近期会持续更新 JSFunction 这里可以找到你经常想要使用的js函数,我正在努力完善它 希望它对你有所帮助 相信代码是优雅的舞者.--北 ...
- Linux c codeblock的使用(三):使用函数库
(一)概念 什么是函数库呢?一下子说概念大家可能不太熟悉,但是这实际上是大家在windows系统上经常见到的东西.没错,就是那些后缀为DLL的文件. linux上实际也有自己的函数库文件,文件类型为. ...
随机推荐
- C语言中储存类别和内存管理
C语言中储存类别和内存管理 储存类别 C语言提供了多种储存类别供我们使用,并且对应的有对应的内存管理策略,在了解C中的储存类型前,我们先了解一下与储存类型相关的一些概念. 1. 基础概念 对象:不同于 ...
- JavaScript初级学习
1. JavaScript的介绍 前身是LiveScript+JavaScript JavaScript(js)是一个脚本语言 基于浏览器的脚本语言 基于对象,面向对象的一个编程语言 2. EcmaS ...
- Keytool 工具使用
Keytool 管理私钥仓库(keystore)和与之相关的 X.509 证书链(用以验证与私钥对应的公钥),也可以用来管理其他信任实体 keytool 将密钥和证书存储在一个所谓的密钥仓库中,缺省的 ...
- docker配置私有镜像仓库-registry和hyper/docker-registry-web
1.前言️ Docker hub是远程仓库,是国外的,push pull速度特别慢,尤其是网速不好的时候,页面都点不进去,官网 但是可以配置阿里云镜像加速哦: 因此搭建一个私有的镜像仓库用于管理我们 ...
- SHA算法摘要处理
byte[] input="sha".getBytes();//待做消息摘要算法的原始信息,可以是任意字符串 MessageDigest sha=MessageDigest.get ...
- 第20 章 : GPU 管理和 Device Plugin 工作机制
GPU 管理和 Device Plugin 工作机制 本文将主要分享以下几个方面的内容: 需求来源 GPU 的容器化 Kubernetes 的 GPU 管理 工作原理 课后思考与实践 需求来源 201 ...
- 学习笔记-git 上传
0.git add * (如果你需要修改源码需要在 1 之前使用,然后再回到 1) 1.git commit -m '提交文字描述' 2.git push -u origin master (上传到主 ...
- 北航OO第一单元作业总结(1.1~1.3)
经过了三次作业之后,OO第一单元告一段落,作为一个蒟蒻,我初步了解了面向对象的编程思想,并将所学内容用于实践. 一.第一次作业 1.架构分析 本次作业需要完成的任务为简单多项式导函数的求解.表达式仅支 ...
- Spring MVC(七篇)
(一)Spring MVC简介 (二)SpringMVC核心控制器 (三)Spring MVC Controller接口控制器详解(一) (三)Spring MVC Controller接口控制器详解 ...
- 美团点评技术专家 帮你快速上手跨平台开发框架Flutter
Flutter并没有开创新的概念,它背后的框架原理和底层设计思想,与原生Android/iOS开发并没有本质区别,甚至从React.Native中吸收了不少优秀的设计理念. Flutter是Googl ...