天天看點

逆向 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​

    ​ 個小時
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 逆向分析:首先進入​

    ​main​

    ​​ 函數,由于​

    ​time​

    ​​ 函數傳入的參數為​

    ​NULL​

    ​​,是以将​

    ​0​

    ​​ 壓入棧之後調用​

    ​time​

    ​ 函數
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 進入函數後進行棧頂和棧底的操作,之後直接通過​

    ​jmp​

    ​​ 跳轉到​

    ​msvcrt._time32​

    ​ 的位址,然後繼續向下調試
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 單步到這個位置可以發現在​

    ​time​

    ​​ 函數中直接調用了​

    ​GetSystemTimeAsFileTime()​

    ​ 這個 API 函數,這個函數屬于底層函數,是作業系統直接提供的接口函數
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 看一下微軟文檔中給出的定義,從函數功能上可以看出這個​

    ​API​

    ​​ 函數可以實時的擷取系統時間,且擷取到的時間是​

    ​UTC​

    ​​ 格式。從參數上來看,傳入的參數是一個指向​

    ​FILETIME​

    ​ 結構體的指針
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 再來看一下​

    ​FILETIME​

    ​​ 結構體,有兩個資料成員都是​

    ​DWORD​

    ​​ 格式(​

    ​4​

    ​​ 個位元組),​

    ​dwLowDateTime​

    ​​ 表示低位時間,而​

    ​dwHighDateTime​

    ​​ 表示高位的時間,關于高位時間和低位時間的差別會在下面說到,值得注意的是時間的機關是​

    ​100​

    ​ 納秒
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 再來看一下調用​

    ​GetSystemTimeAsFileTime​

    ​​ API 函數的例子,​

    ​lea eax,[local]​

    ​​ 這個指令是取函數中第二個局部變量的位址并且存放到​

    ​eax​

    ​​ 當中,再将​

    ​eax​

    ​​ 壓入棧中之後調用函數,結合上面​

    ​GetSystemTimeAsFileTime​

    ​​ 函數的文檔的分析可以知道​

    ​eax​

    ​​ 其實就是​

    ​FILETIME​

    ​ 結構體
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 調用完​

    ​GetSystemTimeAsFileTime​

    ​​ 函數之後,會将​

    ​FILETIME​

    ​​ 結構體的​

    ​dwLowDateTime​

    ​​ 儲存在​

    ​ecx​

    ​​ 當中,将​

    ​dwHighDateTime​

    ​​ 儲存在​

    ​eax​

    ​ 當中
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 還記得上面的文檔嗎 ?​

    ​GetSystemTimeAsFileTime​

    ​​ 函數傳回的時間格式是​

    ​UTC​

    ​​ 時間格式,且是從​

    ​1601-01-01​

    ​​ 開始計時的,機關為​

    ​100​

    ​​ 納秒,而​

    ​time​

    ​​ 函數傳回的時間則是從​

    ​1970-01-01​

    ​​ 開始計時的,機關為秒,是以下面會進行​

    ​UTC​

    ​​ 格式的時間轉換。首先會将時間的高位加上​

    ​0xfe624e21​

    ​​,低位加上​

    ​2AC18000​

    ​​,這一步的目的就是将​

    ​1601-01-01​

    ​​ 調整到​

    ​1970-01-01​

  • 逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數
  • 以高位為例子,調整前為​

    ​0x01d5122f​

    ​​ 而調整後為​

    ​0x00376050​

    ​​,用前減去後結果為​

    ​0x19db1df​

  • 逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數
  • 轉換為​

    ​10​

    ​ 進制
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 由于是以​

    ​100​

    ​​ 納秒為機關,是以乘以​

    ​100​

    ​​ 得出為​

    ​369​

    ​​ 年,而​

    ​1970​

    ​​ 減去​

    ​1601​

    ​​ 剛剛為​

    ​369​

    ​ 年
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 時間轉換之後,調用如下函數,這個函數的作用是将納秒轉換為秒
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 進入這個函數看一下,首先取出第四個參數判斷是否為​

    ​0​

    ​​,之後取出第三個參數​

    ​10000000​

    ​​,然後将低位和高位的時間分别處以​

    ​100000000​

    ​ 即可轉換為秒機關
  • 逆向 time.h 函數庫 time、gmtime 函數
注:​

​1​

​​ 秒等于十億納秒,而上述時間機關為 ​

​100​

​​ 納秒,是以轉換為秒隻需要除以 ​

​1​

​ 千萬即可
  • 最後傳回自​

    ​1970​

    ​ 年以來的秒數
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 在​

    ​time​

    ​​ 函數的最後會判斷傳入的參數是否為​

    ​0​

    ​​,如果不為​

    ​0​

    ​,則将結果放入傳入的變量内
  • 逆向 time.h 函數庫 time、gmtime 函數

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
#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) 擷取纖程局部儲存​

    ​fls​

    ​​ (2)申請堆空間儲存​

    ​tm​

    ​​ 結構體(3)對傳入的​

    ​time​

    ​​ 傳回的參數開始轉換,轉換的結果放入​

    ​tm​

    ​​ 結構體當中(4)傳回​

    ​tm​

    ​ 結構體的指針,函數調用結束
  • 運作結果如下圖所示:
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 下面開始逆向分析,由于計算機處理資料和人的計算方式有很大的不同,是以逆向其中的算法還是比較爽的。首先找到​

    ​main​

    ​​ 函數的入口點,這裡用的是​

    ​Cfree-5​

    ​​ 編譯是以​

    ​main​

    ​​ 入口點比較好找,如果是微軟的​

    ​VS​

    ​​ 編譯的話就需要别的方法了,因為在 main 函數之前的初始化工作太複雜了。在​

    ​main​

    ​​ 函數的入口處可以很清楚的看到首先調用了​

    ​time​

    ​​ 函數,傳回值儲存在​

    ​eax​

    ​​ 當中,之後通過​

    ​push eax​

    ​​ 将其壓入棧中,然後調用​

    ​gmtime​

    ​​ 函數,最後調用列印函數​

    ​printf​

  • 逆向 time.h 函數庫 time、gmtime 函數
  • 查詢一下​

    ​eax​

    ​​ 中的值為​

    ​0x240FF1C​

  • 逆向 time.h 函數庫 time、gmtime 函數
  • 由于傳入的是一個位址,是以根據​

    ​eax​

    ​​ 查詢其指向的位址,可以發現值為​

    ​0x5CEA203A​

    ​​,需要注意的是機關是秒,為什麼是倒過來讀呢,因為​

    ​time​

    ​​ 函數傳回的是數字類型,是以是以小尾的方式儲存在記憶體空間中,其大小為​

    ​4​

    ​ 個位元組
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 将​

    ​0x5CEA203A​

    ​​ 轉換成年機關,得到​

    ​49.4307314​

    ​​ 年,剛剛說了這個時間是從​

    ​1970-01-01​

    ​​ 開始算的,以年為機關加上​

    ​49​

    ​​ 的結果剛好是​

    ​2019​

    ​ 年
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 接下來​

    ​F7​

    ​​ 進入​

    ​gmtime​

    ​​ 函數看看,開始的時候主要操作棧頂和棧底,這個對分析函數沒什麼用處,直接跳轉到​

    ​mscrt._gmtime32​

    ​ 即可
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 跳轉過後發現會調用兩個子函數,逆向之後發現第一個函數主要功能是擷取纖程局部儲存​

    ​FLS​

    ​​,并且申請堆空間用于存放​

    ​tm​

    ​ 結構體;而第二個函數則是核心函數,主要負責時間的轉換
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 首先看一下第一個函數把,由于功能比較簡單就不單步調試了。如圖和注釋所示,有兩個子函數,第一個是擷取纖程局部儲存​

    ​FLS​

    ​​,而第二個函數是申請堆空間,而​

    ​msvcrt,_error​

    ​ 函數主要用作錯誤處理
  • 逆向 time.h 函數庫 time、gmtime 函數
注:調用一些比較複雜的系統 ​

​API​

​​ 函數需要非常小心,因為容易出錯,是以 ​

​error​

​​ 函數用的非常多。但是需要注意的是 ​

​error​

​​ 的使用要注意多線程問題,防止多個線程對用一個 ​

​error​

​ 變量進行争搶
  • 下面就是擷取纖程局部儲存的函數,其中調用了系統​

    ​API​

    ​​ 函數​

    ​FlsGetValue​

    ​​,并且使用​

    ​GetLastError​

    ​​ 函數設定錯誤資訊。當時逆向的時候也是查閱了很多的資料但沒有​

    ​FLS​

    ​​ 和​

    ​FlsGetValue​

    ​ 的資料可供了解,是以尚不清楚這個調用這個函數的目在哪裡。
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 由于​

    ​FlsGetValue​

    ​​ 調用成功了,是以直接跳轉到如下位置,之後通過​

    ​SetLastError​

    ​​ 設定錯誤碼為最後一次擷取到的錯誤碼,也就是剛剛​

    ​GetLastError​

    ​​ 函數擷取到的錯誤碼,最後傳回​

    ​FlsGetValue​

    ​​ 的傳回值,也就是擷取到的局部儲存​

    ​FLS​

    ​ 的位址
  • 逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數
  • 調用完擷取纖程局部儲存的函數,之後看看擷取堆空間的函數,可以看出調用這個函數隻有一個參數​

    ​0x24​

    ​,應該是申請堆空間的大小
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 進入申請堆空間函數,從函數的運作流程可以大緻的得出這個函數主要是通過循環的方式調用​

    ​malloc​

    ​​ 申請堆空間,申請堆空間的大小就是傳入的第一個參數​

    ​0x24​

    ​​,如果​

    ​malloc​

    ​​ 調用失敗的話就通過​

    ​sleep​

    ​​ 函數隔段時間後再次調用,直到超出了某些限制值。如果​

    ​malloc​

    ​ 調用成功,那麼該函數則傳回申請堆空間的首位址
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 運作完申請堆空間的函數後将堆空間的首位址儲存在​

    ​FLS​

    ​​ 偏移​

    ​44​

    ​ 個位元組的地方,之後再次傳回堆空間的首位址
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 這樣一來第一個函數就分析完了,下面來到第二個函數,這個函數就是轉換時間的核心函數。從圖中可以看出,這個函數傳入了兩個參數,第一個參數是申請的堆空間的首位址,第二個參數是​

    ​time​

    ​ 函數傳回的時間,兩個參數的作用就不再多述了
  • 逆向 time.h 函數庫 time、gmtime 函數
  • ​F7​

    ​ 進入這個函數,開始單步調試。首先從參數中取出堆空間的首位址,之後判斷是否申請成功,如果申請成功的話就跳過設定錯誤資訊的步驟
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 然後初始化堆空間,其實就是将堆空間覆寫為​

    ​FFFF...​

    ​,成功之後再次跳轉,目的是忽略設定異常的步驟
  • 逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數
  • 之後從參數中取出​

    ​time​

    ​​ 函數的傳回值,并且和​

    ​0xFFFF5740​

    ​​ 做比較,說明該時間不能大于​

    ​136​

    ​ 年,成功之後再次跳轉
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 還記得​

    ​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​

    ​ 公式去計算餘數,直接取出餘數不行嗎
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 來分析一下​

    ​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​

    ​ 當中儲存的就是年數
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 之後将​

    ​eax​

    ​​ 存入​

    ​tm​

    ​​ 結構體偏移​

    ​0x14​

    ​​ 的位置,也就是​

    ​int tm_year​

    ​​ 在結構體​

    ​tm​

    ​​中的位置,其中​

    ​ebx​

    ​​ 指向的就是​

    ​tm​

    ​ 結構體的位址,而且用的是類似數組的尋址
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 既然知道了​

    ​ebx​

    ​​ 是​

    ​tm​

    ​​ 結構體的位址,那麼下面逆向起來就快了,因為了解時間格式便于逆向其中的算法。完成了年的轉換之後接下來根據​

    ​tm​

    ​​ 結構體成員變量的位置就可以推出下面轉換的是天數,首先将​

    ​0x15180​

    ​​ 放入​

    ​ecx​

    ​​ 中,接着将餘下的年數除以​

    ​0x15180​

    ​​ 得出一年當中的第幾天(餘下的年數就是上面轉換年數的餘數,以秒表示),将商存入​

    ​tm​

    ​​ 結構體偏移​

    ​0x1C​

    ​​ 的位置,餘數存入​

    ​esi​

    ​ 中
  • 逆向 time.h 函數庫 time、gmtime 函數

​0x15180​

​​ 十進制表示為 ​

​86400​

​​ 秒,剛好為 ​

​1​

​ 天
逆向 time.h 函數庫 time、gmtime 函數
  • 接下來轉換月份,就是處于一年當中的第幾個月,範圍是​

    ​0 - 11​

    ​​,​

    ​0​

    ​​ 表示​

    ​1​

    ​​ 月,怎麼轉換的呢:通過循環比較​

    ​ecx + 4​

    ​​ 位址往後的值進行比較,如果大于就跳轉。最後将月份儲存在​

    ​tm​

    ​​ 結構體偏移​

    ​0x10​

    ​ 的地方
  • 逆向 time.h 函數庫 time、gmtime 函數
  • ​ecx + 4​

    ​​ 之後的值其實就是月份疊加起來的值,比如​

    ​1​

    ​​ 月就是​

    ​1E(30天)​

    ​​,2 月就是​

    ​3A(58天=1月+2月)​

    ​​,​

    ​edi​

    ​​ 中記錄着月份的值,且每次循環加​

    ​1​

    ​​。那為什麼​

    ​1​

    ​​ 月是​

    ​30​

    ​​ 天,​

    ​2​

    ​​ 月是​

    ​58​

    ​​ 天,怎麼都少了一天呢,因為​

    ​edi​

    ​​ 初始值就為​

    ​1​

    ​​,是以是在​

    ​1​

    ​ 月的基礎上加的
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 然後轉換的是一月當中的第幾天,這個比較簡單,隻需要将一年當中第幾天減去​

    ​ecx + 4​

    ​​ 的數組中表示的最大月數即可,計算結果為​

    ​1A(26天)​

    ​​。結果儲存在​

    ​tm​

    ​​ 結構體偏移​

    ​0xC​

    ​ 的位置
  • 逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數
  • 完成了一月當中的第幾天的轉換後,下面轉換的是一周當中的第幾天,算法很簡單:首先取出​

    ​time​

    ​​ 函數傳回的秒數,之後除以​

    ​0x15180(1天)​

    ​​ 得到 1 年當中第幾天,之後再除以​

    ​7​

    ​​,餘數​

    ​edx​

    ​​ 就是一周當中第幾天。結果儲存在​

    ​tm​

    ​​ 結構體偏移​

    ​0x18​

    ​ 的位置
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 最後就是轉換時分秒了,由于算法比較簡單就統一說了:(1)小時的轉換是用上面餘下的天數除以​

    ​0x1E0(3600秒)​

    ​​ (2)分的轉換是使用餘下的小時數除以​

    ​3C(60秒)​

    ​​ (3)秒的轉化就是餘下的秒數,這個不需要計算(4)最後将這三個值分别存入​

    ​tm​

    ​​ 結構體偏移​

    ​0x8、0x4、0x0​

    ​ 的地方
  • 逆向 time.h 函數庫 time、gmtime 函數
  • 最後傳回堆空間的首位址,也就是​

    ​tm​

    ​​ 結構體的位址。如圖所示​

    ​tm​

    ​​ 結構體的所有變量都已經被覆寫成轉換後的值。需要注意的是傳回值通過​

    ​esi​

    ​​ 傳回,而不是一般的​

    ​eax​

  • 逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數
    逆向 time.h 函數庫 time、gmtime 函數