前言

  RunAsDate是一個小工具,允許您在指定的日期和時間執行程式,不過有人用它來破解有時間限制了。此實用程式不會更改計算機的當前系統日期和時間,但只會將指定的日期/時間注入所需的應用程式。該軟體是個免費軟體,可以通過 官網 下載。有一天想看看它到底怎麼實現的。經過分析是通過注入dllHook幾個關於時間獲取API實現的。32位的和64位的程式碼實現沒啥區別,本篇以64位進行分析,32位感興趣自行分析。

主角和工具

  • Detect it easy 1.01
  • IDA 7.5
  • X64Dbg
  • RunAsDate

探測

  我們先到官網下載原汁原味的程式,如下圖所示進行下載:

  解壓後,得到了3個檔案:

  執行該軟體,介面如下:

  為了檢驗工具的效果,先寫一個獲取時間的C程式碼(隨便一個語言就可,不過程式碼需要自己寫),如下:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int main()
{
time_t t;
time(&t);
struct tm _t;
errno_t err = localtime_s(&_t, &t);
err ? puts("獲取時間失敗!!!") : printf_s("%d/%0.2d/%0.2d\n", _t.tm_year + 1900, _t.tm_mon + 1, _t.tm_mday);
system("pause");
return 0;
}

  編譯執行,獲取得到當前時間:

  來試試工具有沒有效果,注意把下圖所示的紅框框住的選項選中,否則沒效果,然後點選執行,發現起作用:

初步分析

  既然工具咋用已經探測完畢了,我們來進入分析環節。先用Detect it easy 1.01探測一下:

  是C++寫的,且沒有任何加殼,為逆向分析降低了足夠的難度。直接拖到IDA進行抄底。啟動一個新程序的API函式有很多,有ShellExecuteEx系列函式、ShellExecute系列函式和CreateProcess系例函式。我們都給下上斷點,來看看它到底是用哪個函式啟動的,設定好點選執行,斷點斷到CreateProcessW,如下圖所示。

  通過堆疊定位可以定位到呼叫地址,如下圖所示:

  定位到IDA中,然後大體分析一下函式,重新命名一些函式方便進一步分析,並發現一些可疑函式,命名好名字,關鍵虛擬碼如下面幾張圖所示:

  下圖是執行上面子函式的主過程。有經驗的一看就知道,這就是典型的遠端執行緒注入過程。

  什麼是遠端執行緒注入呢?首先你準備一個待注入的Dll,假設名字為A.DLL。如果載入一個Dll,常規的辦法就是用LoadLibrary函式載入。然而我是A程序,想讓B程序呼叫函式,就必須啟用一個執行緒,需要呼叫的API就是CreateRemoteThread函式,只能通過lpParameter傳遞一個引數。它們的函式原型如下:

HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
); HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);

  而每一個程序中,LoadLibrary函式地址都是一致的。故我需要通過一段ShellCode,將一些引數聚合起來直接傳給它,讓它負責分析傳來的引數並實現注入功能。而ShellCode怎樣寫到B程序呢?通過VirtualAllocate(Ex)函式和WriteProcessMemory函式配合將ShellCode和待解析的引數一塊寫進去。從IDA分析結果可知,ShellCode就是sub_140006B3C,我們來看一下:

  為了方便閱讀,偽C程式碼如下:

  從ShellCode很明顯看出。a1就是通過CreateRemoteThread傳來的引數。a1的地址加40偏移的值就是一個函式地址,在把a1的地址加上64的偏移作為引數傳入得到返回值v2v3應該是一個函式地址,通過a1加上48的偏移,把v2和a1加上586的偏移作為引數傳入得到返回值v3,如果返回值不為空呼叫v3,把a1加上620的偏移作為引數進行傳入。只從ShellCode看不太出來是啥,如果有經驗就應該大體能猜出來。具體分析一下它傳過來的引數是如何包裝的。

  從上圖發現*(lpFileName + 66)就是所謂待注入程序呼叫ShellCode的引數。而從Buffer儲存寫進去,但遺憾的是,我壓根不知道Buffer裡面的內容到底是啥,看前面的虛擬碼也看不出來,只看到它的首地址被清零。這個就是F5外掛的缺陷。它反編譯成偽C程式碼不是準確的,甚至是錯誤的,比如函式呼叫的引數有時會有錯誤,或者直接JMPOUT,還是看彙編準確。不過我們可以看一下BufferIDA識別為char,但明顯看到使用該變數的函式告訴咱們這個是大小至少為0x288char陣列。故我們可以看看Buffer的堆疊來進行修正。

  從堆疊可以明顯看出所謂是fucs就是屬於Buffer的,所以上面的部分就是給引數打包的過程。我們直接改Buffer,將它改為char Buffer[288]。然後重新F5,看一下程式碼的變化:

  反編譯出來的偽C程式碼就看起來正常了,雖然看起來還是比較彆扭,不過包裝引數的過程比較明晰了。然後我們把它拖到X64Dbg,來進行驗證一下過程,我們先通過下VirtualAllocEx斷點獲取它們返回的申請到的物理頁虛擬首地址,把它記錄下來。

  然後在CreateRemoteThread下斷點,停住建立的待注入的程式。然後另起一個X64Dbg附加上,直接看一下記錄的地址的內容。首先我看的是引數部分,直接在記憶體視窗選好0x288位元組儲存到檔案,方便一一對應,如下圖所示:

  下圖就是所謂的ShellCode,在函式頭部下一個斷點:

  然後放開RunAsDate程式,被注入的程式就停在上一步下的斷點上,然後我們一步一步的跟,看看呼叫的是什麼函式,先跟到第一個,發現是LoadLibraryW函式:

  其次就是GetProcAddress函式,用來獲取DllInitDate函式地址:

  然後校驗獲取是否成功,傳參FILETIME結構體呼叫InitDate函式:

  根據IDA和X64Dbg的分析,每個資料的對應如下圖所示:

深入分析

  通過初步分析我們知道了這個軟體的基本原理。但功能實現全部在所謂的dateinjo1_64.dll當中。我們可以在注入的程式時,直接從臨時資料夾薅出來。在之前函式分析也知道它是直接把資源釋放出來的,直接用資源編輯工具也能提取出來,這我就不繼續描述了,直接開始分析。具體分析過程就不詳細描述了,完全憑自己的正向開發經驗,這個Dll很簡單,最後命名好名字後如下圖所示:

  綜上可以看出,它是通過對GetLocalTimeGetSystemTimeGetSystemTimeAsFileTime這三個函式進行掛鉤子。怎麼掛鉤子的可以在詳細介紹一下:

  可以看出,它是通過構造一個movjmp+死地址進行Hook,感覺不直觀,來看一下下面一個被RunAsDate注入的被掛鉤的函式:

  通過上圖,知道為什麼程式碼要這樣構造了吧?

總結與思考

  • RunAsDate並不高大上,原理很簡單,其實本質就是用了遠端執行緒注入+Hook所有獲取時間函式的API的方式實現時間的控制。
  • 有些人利用RunAsDate,來破解一些軟體時間使用限制。想要保護自己商業軟體的合法權益,可以通過它的實現原理,檢查是否程式模組有沒有它的Dll,有的話直接退出程式或者修復Hook。也可以檢查函式是否被Hook的方式進行反制。
  • 使用IDA時,不要過於依賴F5,雖然直觀方便,但它反編譯出的偽C程式碼並不是完全正確的,甚至是錯誤的,如果想弄對必須進行修改調整。
  • 軟體的Hash值:EF847F60C02856AB013438D7A55A6CC1
  • 64位的注入的Dll的IDA分析結果:藍奏雲下載 —— 密碼:f854