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
這個 API 函數,這個函數屬于底層函數,是作業系統直接提供的接口函數GetSystemTimeAsFileTime()
- 看一下微軟文檔中給出的定義,從函數功能上可以看出這個
函數可以實時的擷取系統時間,且擷取到的時間是API
格式。從參數上來看,傳入的參數是一個指向UTC
結構體的指針FILETIME
- 再來看一下
結構體,有兩個資料成員都是FILETIME
格式(DWORD
個位元組),4
表示低位時間,而dwLowDateTime
表示高位的時間,關于高位時間和低位時間的差別會在下面說到,值得注意的是時間的機關是dwHighDateTime
納秒100
- 再來看一下調用
API 函數的例子,GetSystemTimeAsFileTime
這個指令是取函數中第二個局部變量的位址并且存放到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
函數
gmtime
- 函數原型:
struct tm *gmtime(const time_t *timer)
- 函數功能:C 庫函數
使用struct tm *gmtime(const time_t *timer)
函數傳回的值來填充time
結構,并用協調世界時(UTC)也被稱為格林尼治标準時間(GMT)表示tm
- C\C++ 實作:
#include <stdio.h>
#include <time.h>
#define
#define
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) 擷取纖程局部儲存
(2)申請堆空間儲存fls
結構體(3)對傳入的tm
傳回的參數開始轉換,轉換的結果放入time
結構體當中(4)傳回tm
結構體的指針,函數調用結束tm
- 運作結果如下圖所示:
- 下面開始逆向分析,由于計算機處理資料和人的計算方式有很大的不同,是以逆向其中的算法還是比較爽的。首先找到
函數的入口點,這裡用的是main
編譯是以Cfree-5
入口點比較好找,如果是微軟的main
編譯的話就需要别的方法了,因為在 main 函數之前的初始化工作太複雜了。在VS
函數的入口處可以很清楚的看到首先調用了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
函數傳回的秒數轉換成年,具體算法:(1)通過time
得到多少年,且餘數0x5CEA203A / 0x7861F80
約在edx
年之間(2)使用1 - 4
公式計算出餘數(3)根據餘數加上固定的年數得到一共多少年,如果是5CEA203A / 7861F80 * F879E080 + 5CEA203A
年就加上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
,2 月就是1E(30天)
,3A(58天=1月+2月)
中記錄着月份的值,且每次循環加edi
。那為什麼1
月是1
天,30
月是2
天,怎麼都少了一天呢,因為58
初始值就為edi
,是以是在1
月的基礎上加的1
- 然後轉換的是一月當中的第幾天,這個比較簡單,隻需要将一年當中第幾天減去
的數組中表示的最大月數即可,計算結果為ecx + 4
。結果儲存在1A(26天)
結構體偏移tm
的位置0xC
- 完成了一月當中的第幾天的轉換後,下面轉換的是一周當中的第幾天,算法很簡單:首先取出
函數傳回的秒數,之後除以time
得到 1 年當中第幾天,之後再除以0x15180(1天)
,餘數7
就是一周當中第幾天。結果儲存在edx
結構體偏移tm
的位置0x18
- 最後就是轉換時分秒了,由于算法比較簡單就統一說了:(1)小時的轉換是用上面餘下的天數除以
(2)分的轉換是使用餘下的小時數除以0x1E0(3600秒)
(3)秒的轉化就是餘下的秒數,這個不需要計算(4)最後将這三個值分别存入3C(60秒)
結構體偏移tm
的地方0x8、0x4、0x0
- 最後傳回堆空間的首位址,也就是
結構體的位址。如圖所示tm
結構體的所有變量都已經被覆寫成轉換後的值。需要注意的是傳回值通過tm
傳回,而不是一般的esi
eax