前言
RunAsDate
是一個小工具,允許您在指定的日期和時間運行程序,不過有人用它來破解有時間限制了。此實用程序不會更改計算機的當前系統日期和時間,但只會將指定的日期/時間注入所需的應用程序。該軟件是個免費軟件,可以通過 官網 下載。有一天想看看它到底怎么實現的。經過分析是通過注入dll
來Hook
幾個關於時間獲取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的偏移
作為參數傳入得到返回值v2
。v3
應該是一個函數地址,通過a1
加上48的偏移
,把v2
和a1加上586的偏移
作為參數傳入得到返回值v3
,如果返回值不為空調用v3
,把a1
加上620的偏移
作為參數進行傳入。只從ShellCode
看不太出來是啥,如果有經驗就應該大體能猜出來。具體分析一下它傳過來的參數是如何包裝的。
從上圖發現*(lpFileName + 66)
就是所謂待注入進程調用ShellCode
的參數。而從Buffer
存儲寫進去,但遺憾的是,我壓根不知道Buffer
里面的內容到底是啥,看前面的偽代碼也看不出來,只看到它的首地址被清零。這個就是F5
插件的缺陷。它反編譯成偽C代碼不是准確的,甚至是錯誤的,比如函數調用的參數有時會有錯誤,或者直接JMPOUT
,還是看匯編准確。不過我們可以看一下Buffer
被IDA
識別為char
,但明顯看到使用該變量的函數告訴咱們這個是大小至少為0x288
的char數組
。故我們可以看看Buffer
的堆棧來進行修正。
從堆棧可以明顯看出所謂的fucs
就是屬於Buffer
的,所以上面的部分就是給參數打包的過程。我們直接改Buffer
,將它改為char Buffer[0x288]
。然后重新F5
,看一下代碼的變化:
反編譯出來的偽C代碼就看起來正常了,雖然看起來還是比較別扭,不過包裝參數的過程比較明晰了。然后我們把它拖到X64Dbg
,來進行驗證一下過程,我們先通過下VirtualAllocEx
斷點獲取它們返回的申請到的物理頁虛擬首地址,把它記錄下來。
然后在CreateRemoteThread
下斷點,停住創建的待注入的程序。然后另起一個X64Dbg
附加上,直接看一下記錄的地址的內容。首先我看的是參數部分,直接在內存窗口選好0x288
字節保存到文件,方便一一對應,如下圖所示:
下圖就是所謂的ShellCode
,在函數頭部下一個斷點:
然后放開RunAsDate
程序,被注入的程序就停在上一步下的斷點上,然后我們一步一步的跟,看看調用的是什么函數,先跟到第一個,發現是LoadLibraryW
函數:
其次就是GetProcAddress
函數,用來獲取Dll
中InitDate
函數地址:
然后校驗獲取是否成功,傳參FILETIME
結構體調用InitDate
函數:
根據IDA和X64Dbg的分析,每個數據的對應如下圖所示:
深入分析
通過初步分析我們知道了這個軟件的基本原理。但功能實現全部在所謂的dateinjo1_64.dll
當中。我們可以在注入的程序時,直接從臨時文件夾薅出來。在之前函數分析也知道它是直接把資源釋放出來的,直接用資源編輯工具也能提取出來,這我就不繼續描述了,直接開始分析。具體分析過程就不詳細描述了,完全憑自己的正向開發經驗,這個Dll很簡單,最后命名好名字后如下圖所示:
綜上可以看出,它是通過對GetLocalTime
、GetSystemTime
、GetSystemTimeAsFileTime
這三個函數進行掛鈎子。怎么掛鈎子的可以在詳細介紹一下:
可以看出,它是通過構造一個mov
和jmp eax
進行Hook
,感覺不直觀,來看一下下面一個被RunAsDate
注入的被掛鈎的函數:
通過上圖,知道為什么代碼要這樣構造了吧?
總結與思考
RunAsDate
並不高大上,原理很簡單,其實本質就是用了遠程線程注入
+Hook
所有獲取時間函數的API
的方式實現時間的控制。- 有些人利用
RunAsDate
,來破解一些軟件時間使用限制。想要保護自己商業軟件的合法權益,可以通過它的實現原理,檢查是否程序模塊有沒有它的Dll
,有的話直接退出程序或者修復Hook
。也可以檢查函數是否被Hook
的方式進行反制。 - 使用
IDA
時,不要過於依賴F5
,雖然直觀方便,但它反編譯出的偽C代碼並不是完全正確的,甚至是錯誤的,如果想弄對必須進行修改調整。 - 軟件的
Hash
值:EF847F60C02856AB013438D7A55A6CC1
。 - 64位的注入的Dll的IDA分析結果:藍奏雲下載 —— 密碼:f854