逆向 time.h 函数库 time、gmtime 函数
0x01 time 函數(shù)
函數(shù)原型:time_t time(time_t *t)
函數(shù)功能:返回自紀(jì)元 Epoch(1970-01-01 00:00:00 UTC)起經(jīng)過的時(shí)間,以秒為單位。如果 seconds 不為空,則返回值也存儲(chǔ)在變量 seconds 中
CC++ 實(shí)現(xiàn):
#include <stdio.h>
#include <time.h>
int main ()
{
time_t seconds;
seconds = time(NULL);
printf("自 1970-01-01 起的小時(shí)數(shù) = %ld
", seconds/3600);
return(0);
}
上述程序的功能是通過 time 函數(shù)獲取自 1970-01-01 00:00:00 后經(jīng)過的時(shí)間,之后打印出經(jīng)過的小時(shí)數(shù),程序的運(yùn)行結(jié)果如下圖所示:表示自 1970-01-01 00:00:00 之后經(jīng)過了 432971 個(gè)小時(shí)
逆向分析:首先進(jìn)入 main 函數(shù),由于 time 函數(shù)傳入的參數(shù)為 NULL,所以將 0 壓入棧之后調(diào)用 time 函數(shù)
進(jìn)入函數(shù)后進(jìn)行棧頂和棧底的操作,之后直接通過 jmp 跳轉(zhuǎn)到 msvcrt._time32 的地址,然后繼續(xù)向下調(diào)試
單步到這個(gè)位置可以發(fā)現(xiàn)在 time 函數(shù)中直接調(diào)用了 GetSystemTimeAsFileTime() 這個(gè) API 函數(shù),這個(gè)函數(shù)屬于底層函數(shù),是操作系統(tǒng)直接提供的接口函數(shù)
看一下微軟文檔中給出的定義,從函數(shù)功能上可以看出這個(gè) API 函數(shù)可以實(shí)時(shí)的獲取系統(tǒng)時(shí)間,且獲取到的時(shí)間是 UTC 格式。從參數(shù)上來看,傳入的參數(shù)是一個(gè)指向 FILETIME 結(jié)構(gòu)體的指針
再來看一下 FILETIME 結(jié)構(gòu)體,有兩個(gè)數(shù)據(jù)成員都是 DWORD 格式(4 個(gè)字節(jié)),dwLowDateTime 表示低位時(shí)間,而 dwHighDateTime 表示高位的時(shí)間,關(guān)于高位時(shí)間和低位時(shí)間的區(qū)別會(huì)在下面說到,值得注意的是時(shí)間的單位是 100 納秒
再來看一下調(diào)用 GetSystemTimeAsFileTime API 函數(shù)的例子,lea eax,[local] 這個(gè)命令是取函數(shù)中第二個(gè)局部變量的地址并且存放到 eax 當(dāng)中,再將 eax 壓入棧中之后調(diào)用函數(shù),結(jié)合上面 GetSystemTimeAsFileTime 函數(shù)的文檔的分析可以知道 eax 其實(shí)就是 FILETIME 結(jié)構(gòu)體
調(diào)用完 GetSystemTimeAsFileTime 函數(shù)之后,會(huì)將 FILETIME 結(jié)構(gòu)體的 dwLowDateTime 儲(chǔ)存在 ecx 當(dāng)中,將 dwHighDateTime 儲(chǔ)存在 eax 當(dāng)中
還記得上面的文檔嗎 ? GetSystemTimeAsFileTime 函數(shù)返回的時(shí)間格式是 UTC 時(shí)間格式,且是從 1601-01-01 開始計(jì)時(shí)的,單位為 100 納秒,而 time 函數(shù)返回的時(shí)間則是從 1970-01-01 開始計(jì)時(shí)的,單位為秒,所以下面會(huì)進(jìn)行 UTC 格式的時(shí)間轉(zhuǎn)換。首先會(huì)將時(shí)間的高位加上 0xfe624e21,低位加上 2AC18000,這一步的目的就是將 1601-01-01 調(diào)整到 1970-01-01
以高位為例子,調(diào)整前為 0x01d5122f 而調(diào)整后為 0x00376050,用前減去后結(jié)果為 0x19db1df
轉(zhuǎn)換為 10 進(jìn)制
由于是以 100 納秒為單位,所以乘以 100 得出為 369 年,而 1970 減去 1601 剛剛為 369 年
時(shí)間轉(zhuǎn)換之后,調(diào)用如下函數(shù),這個(gè)函數(shù)的作用是將納秒轉(zhuǎn)換為秒
進(jìn)入這個(gè)函數(shù)看一下,首先取出第四個(gè)參數(shù)判斷是否為 0,之后取出第三個(gè)參數(shù) 10000000,然后將低位和高位的時(shí)間分別處以 100000000 即可轉(zhuǎn)換為秒單位
注:
1秒等于十億納秒,而上述時(shí)間單位為100納秒,所以轉(zhuǎn)換為秒只需要除以1千萬即可
最后返回自 1970 年以來的秒數(shù)
在 time 函數(shù)的最后會(huì)判斷傳入的參數(shù)是否為 0,如果不為 0,則將結(jié)果放入傳入的變量內(nèi)
0x02 gmtime 函數(shù)
函數(shù)原型:struct tm *gmtime(const time_t *timer)
函數(shù)功能:C 庫函數(shù) struct tm *gmtime(const time_t *timer) 使用 time 函數(shù)返回的值來填充 tm 結(jié)構(gòu),并用協(xié)調(diào)世界時(shí)(UTC)也被稱為格林尼治標(biāo)準(zhǔn)時(shí)間(GMT)表示
CC++ 實(shí)現(xiàn):
#include <stdio.h>
#include <time.h>
#define BST (+1)
#define CCT (+8)
int main ()
{
time_t rawtime;
struct tm *info;
time(&rawtime);
/* 獲取 GMT 時(shí)間 */
info = gmtime(&rawtime);
printf("當(dāng)前的世界時(shí)鐘:
");
printf("倫敦:%2d:%02d
", (info->tm_hour+BST)%24, info->tm_min);
printf("中國:%2d:%02d
", (info->tm_hour+CCT)%24, info->tm_min);
return(0);
}
上述程序的作用主要是獲取由 time 函數(shù)返回的時(shí)間(從 1970.1.1 開始的小時(shí)數(shù)),之后放入 gmtime 函數(shù)轉(zhuǎn)換成更為詳細(xì)的時(shí)間單位。tm 結(jié)構(gòu)體如下圖所示,這個(gè)就是更為精確的時(shí)間細(xì)分:
struct tm {
int tm_sec; /* 秒,范圍從 0 到 59 */
int tm_min; /* 分,范圍從 0 到 59 */
int tm_hour; /* 小時(shí),范圍從 0 到 23 */
int tm_mday; /* 一月中的第幾天,范圍從 1 到 31 */
int tm_mon; /* 月份,范圍從 0 到 11 */
int tm_year; /* 自 1900 起的年數(shù) */
int tm_wday; /* 一周中的第幾天,范圍從 0 到 6 */
int tm_yday; /* 一年中的第幾天,范圍從 0 到 365 */
int tm_isdst; /* 夏令時(shí) */
};
程序運(yùn)行的步驟:(1) 獲取纖程局部儲(chǔ)存 fls (2)申請堆空間儲(chǔ)存 tm 結(jié)構(gòu)體(3)對傳入的 time 返回的參數(shù)開始轉(zhuǎn)換,轉(zhuǎn)換的結(jié)果放入 tm 結(jié)構(gòu)體當(dāng)中(4)返回 tm 結(jié)構(gòu)體的指針,函數(shù)調(diào)用結(jié)束
運(yùn)行結(jié)果如下圖所示:
下面開始逆向分析,由于計(jì)算機(jī)處理數(shù)據(jù)和人的計(jì)算方式有很大的不同,所以逆向其中的算法還是比較爽的。首先找到 main 函數(shù)的入口點(diǎn),這里用的是 Cfree-5 編譯所以 main 入口點(diǎn)比較好找,如果是微軟的 VS 編譯的話就需要?jiǎng)e的方法了,因?yàn)樵?main 函數(shù)之前的初始化工作太復(fù)雜了。在 main 函數(shù)的入口處可以很清楚的看到首先調(diào)用了 time 函數(shù),返回值儲(chǔ)存在 eax 當(dāng)中,之后通過 push eax 將其壓入棧中,然后調(diào)用 gmtime 函數(shù),最后調(diào)用打印函數(shù) printf
查詢一下 eax 中的值為 0x240FF1C
由于傳入的是一個(gè)地址,所以根據(jù) eax 查詢其指向的地址,可以發(fā)現(xiàn)值為 0x5CEA203A,需要注意的是單位是秒,為什么是倒過來讀呢,因?yàn)?time 函數(shù)返回的是數(shù)字類型,所以是以小尾的方式儲(chǔ)存在內(nèi)存空間中,其大小為 4 個(gè)字節(jié)
將 0x5CEA203A 轉(zhuǎn)換成年單位,得到 49.4307314 年,剛剛說了這個(gè)時(shí)間是從 1970-01-01 開始算的,以年為單位加上 49 的結(jié)果剛好是 2019 年
接下來 F7 進(jìn)入 gmtime 函數(shù)看看,開始的時(shí)候主要操作棧頂和棧底,這個(gè)對分析函數(shù)沒什么用處,直接跳轉(zhuǎn)到 mscrt._gmtime32 即可
跳轉(zhuǎn)過后發(fā)現(xiàn)會(huì)調(diào)用兩個(gè)子函數(shù),逆向之后發(fā)現(xiàn)第一個(gè)函數(shù)主要功能是獲取纖程局部儲(chǔ)存 FLS,并且申請堆空間用于存放 tm 結(jié)構(gòu)體;而第二個(gè)函數(shù)則是核心函數(shù),主要負(fù)責(zé)時(shí)間的轉(zhuǎn)換
首先看一下第一個(gè)函數(shù)把,由于功能比較簡單就不單步調(diào)試了。如圖和注釋所示,有兩個(gè)子函數(shù),第一個(gè)是獲取纖程局部儲(chǔ)存 FLS,而第二個(gè)函數(shù)是申請堆空間,而 msvcrt,_error 函數(shù)主要用作錯(cuò)誤處理
注:調(diào)用一些比較復(fù)雜的系統(tǒng)
API函數(shù)需要非常小心,因?yàn)槿菀壮鲥e(cuò),所以error函數(shù)用的非常多。但是需要注意的是error的使用要注意多線程問題,防止多個(gè)線程對用一個(gè)error變量進(jìn)行爭搶
下面就是獲取纖程局部儲(chǔ)存的函數(shù),其中調(diào)用了系統(tǒng) API 函數(shù) FlsGetValue,并且使用 GetLastError 函數(shù)設(shè)置錯(cuò)誤信息。當(dāng)時(shí)逆向的時(shí)候也是查閱了很多的資料但沒有 FLS 和 FlsGetValue 的資料可供了解,所以尚不清楚這個(gè)調(diào)用這個(gè)函數(shù)的目在哪里。
由于 FlsGetValue 調(diào)用成功了,所以直接跳轉(zhuǎn)到如下位置,之后通過 SetLastError 設(shè)置錯(cuò)誤碼為最后一次獲取到的錯(cuò)誤碼,也就是剛剛 GetLastError 函數(shù)獲取到的錯(cuò)誤碼,最后返回 FlsGetValue 的返回值,也就是獲取到的局部儲(chǔ)存 FLS 的地址
調(diào)用完獲取纖程局部儲(chǔ)存的函數(shù),之后看看獲取堆空間的函數(shù),可以看出調(diào)用這個(gè)函數(shù)只有一個(gè)參數(shù) 0x24,應(yīng)該是申請堆空間的大小
進(jìn)入申請堆空間函數(shù),從函數(shù)的運(yùn)行流程可以大致的得出這個(gè)函數(shù)主要是通過循環(huán)的方式調(diào)用 malloc 申請堆空間,申請堆空間的大小就是傳入的第一個(gè)參數(shù) 0x24,如果 malloc 調(diào)用失敗的話就通過 sleep 函數(shù)隔段時(shí)間后再次調(diào)用,直到超出了某些限制值。如果 malloc 調(diào)用成功,那么該函數(shù)則返回申請堆空間的首地址
運(yùn)行完申請堆空間的函數(shù)后將堆空間的首地址儲(chǔ)存在 FLS 偏移 44 個(gè)字節(jié)的地方,之后再次返回堆空間的首地址
這樣一來第一個(gè)函數(shù)就分析完了,下面來到第二個(gè)函數(shù),這個(gè)函數(shù)就是轉(zhuǎn)換時(shí)間的核心函數(shù)。從圖中可以看出,這個(gè)函數(shù)傳入了兩個(gè)參數(shù),第一個(gè)參數(shù)是申請的堆空間的首地址,第二個(gè)參數(shù)是 time 函數(shù)返回的時(shí)間,兩個(gè)參數(shù)的作用就不再多述了
F7 進(jìn)入這個(gè)函數(shù),開始單步調(diào)試。首先從參數(shù)中取出堆空間的首地址,之后判斷是否申請成功,如果申請成功的話就跳過設(shè)置錯(cuò)誤信息的步驟
然后初始化堆空間,其實(shí)就是將堆空間覆蓋為 FFFF...,成功之后再次跳轉(zhuǎn),目的是忽略設(shè)置異常的步驟
之后從參數(shù)中取出 time 函數(shù)的返回值,并且和 0xFFFF5740 做比較,說明該時(shí)間不能大于 136 年,成功之后再次跳轉(zhuǎn)
還記得 tm 結(jié)構(gòu)體嗎,首先做的轉(zhuǎn)換就是將 time 函數(shù)返回的秒數(shù)轉(zhuǎn)換成年,具體算法:(1)通過 0x5CEA203A / 0x7861F80 得到多少年,且余數(shù) edx 約在 1 - 4 年之間(2)使用 5CEA203A / 7861F80 * F879E080 + 5CEA203A 公式計(jì)算出余數(shù)(3)根據(jù)余數(shù)加上固定的年數(shù)得到一共多少年,如果是 1.3 年就加上 1 年;2.4 年就加上 2 年
機(jī)器的 CPU 計(jì)算方法和人的計(jì)算方法有很大的不同,最大的難點(diǎn)就是為何使用 5CEA203A / 7861F80 * F879E080 + 5CEA203A 公式去計(jì)算余數(shù),直接取出余數(shù)不行嗎
來分析一下 5CEA203A / 7861F80 * F879E080 + 5CEA203A 取余公式:
原式等于: 5CEA203A / 7861F80 * F879E080 + 5CEA203A
= C * F879E080 + 5CEA203A
= BA5B68600 + 5CEA203A
= A5B68600 + 5CEA203A
= 102A0A63A
= 02A0A63A
可能有點(diǎn)難理解,轉(zhuǎn)換一下就行了:
原式等于: 原數(shù) / 7861F80 * F879E080 + 原數(shù)
= 商 * F879E080 + 原數(shù)
= BA5B68600 + 原數(shù)
= A5B68600 + 原數(shù)
= 102A0A63A
= 余數(shù)
之后還需要考慮到溢出:
原式等于: 原數(shù) / 7861F80 * F879E080 + 原數(shù) - B00000000 - 100000000
= 商 * F879E080 + 原數(shù)- B00000000 - 100000000
= BA5B68600 + 原數(shù) - B00000000 - 100000000
= A5B68600 + 原數(shù) - 100000000
= 102A0A63A - 100000000
= 02A0A63A
= 余數(shù)
而人的計(jì)算方式是這樣的:
原數(shù) / 7861F80 = 商 ... 余數(shù) => 余數(shù) = 原數(shù) - 商 * 7861F80
所以將上面的式子化簡之后,和 商 * 7861F80 + 余數(shù) = 原數(shù) 其實(shí)是一樣的:
原數(shù) / 7861F80 * F879E080 + 原數(shù) - B00000000 - 100000000 = 余數(shù)
原數(shù) / 7861F80 * F879E080 + 原數(shù) - C00000000 = 余數(shù)
C * F879E080 + 原數(shù) - C00000000 = 余數(shù)
C * (F879E080 - 100000000) + 原數(shù) = 余數(shù)
C * -7861F80 + 原數(shù) = 余數(shù)
原數(shù) - 商 * 7861F80 = 余數(shù)
eax 當(dāng)中儲(chǔ)存的就是年數(shù)
之后將 eax 存入 tm 結(jié)構(gòu)體偏移 0x14 的位置,也就是 int tm_year 在結(jié)構(gòu)體 tm中的位置,其中 ebx 指向的就是 tm 結(jié)構(gòu)體的地址,而且用的是類似數(shù)組的尋址
既然知道了 ebx 是 tm 結(jié)構(gòu)體的地址,那么下面逆向起來就快了,因?yàn)槔斫鈺r(shí)間格式便于逆向其中的算法。完成了年的轉(zhuǎn)換之后接下來根據(jù) tm 結(jié)構(gòu)體成員變量的位置就可以推出下面轉(zhuǎn)換的是天數(shù),首先將 0x15180 放入 ecx 中,接著將余下的年數(shù)除以 0x15180 得出一年當(dāng)中的第幾天(余下的年數(shù)就是上面轉(zhuǎn)換年數(shù)的余數(shù),以秒表示),將商存入 tm 結(jié)構(gòu)體偏移 0x1C 的位置,余數(shù)存入 esi 中
0x15180十進(jìn)制表示為86400秒,剛好為1天
接下來轉(zhuǎn)換月份,就是處于一年當(dāng)中的第幾個(gè)月,范圍是 0 - 11,0 表示 1 月,怎么轉(zhuǎn)換的呢:通過循環(huán)比較 ecx + 4 地址往后的值進(jìn)行比較,如果大于就跳轉(zhuǎn)。最后將月份儲(chǔ)存在 tm 結(jié)構(gòu)體偏移 0x10 的地方
ecx + 4 之后的值其實(shí)就是月份疊加起來的值,比如 1 月就是 1E(30天),2 月就是 3A(58天=1月+2月),edi 中記錄著月份的值,且每次循環(huán)加 1。那為什么 1 月是 30 天,2 月是 58 天,怎么都少了一天呢,因?yàn)?edi 初始值就為 1,所以是在 1 月的基礎(chǔ)上加的
然后轉(zhuǎn)換的是一月當(dāng)中的第幾天,這個(gè)比較簡單,只需要將一年當(dāng)中第幾天減去 ecx + 4 的數(shù)組中表示的最大月數(shù)即可,計(jì)算結(jié)果為 1A(26天)。結(jié)果儲(chǔ)存在 tm 結(jié)構(gòu)體偏移 0xC 的位置
完成了一月當(dāng)中的第幾天的轉(zhuǎn)換后,下面轉(zhuǎn)換的是一周當(dāng)中的第幾天,算法很簡單:首先取出 time 函數(shù)返回的秒數(shù),之后除以 0x15180(1天) 得到 1 年當(dāng)中第幾天,之后再除以 7,余數(shù) edx 就是一周當(dāng)中第幾天。結(jié)果儲(chǔ)存在 tm 結(jié)構(gòu)體偏移 0x18 的位置
最后就是轉(zhuǎn)換時(shí)分秒了,由于算法比較簡單就統(tǒng)一說了:(1)小時(shí)的轉(zhuǎn)換是用上面余下的天數(shù)除以 0x1E0(3600秒) (2)分的轉(zhuǎn)換是使用余下的小時(shí)數(shù)除以 3C(60秒) (3)秒的轉(zhuǎn)化就是余下的秒數(shù),這個(gè)不需要計(jì)算(4)最后將這三個(gè)值分別存入 tm 結(jié)構(gòu)體偏移 0x8、0x4、0x0 的地方
最后返回堆空間的首地址,也就是 tm 結(jié)構(gòu)體的地址。如圖所示 tm 結(jié)構(gòu)體的所有變量都已經(jīng)被覆蓋成轉(zhuǎn)換后的值。需要注意的是返回值通過 esi 返回,而不是一般的 eax
逆向
time、gmtime函數(shù)到此結(jié)束,如有錯(cuò)誤,歡迎指正
總結(jié)
以上是生活随笔為你收集整理的逆向 time.h 函数库 time、gmtime 函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Visual Studio Code里
- 下一篇: C# vb .net图像合成-多图片叠加