前言
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[288]
。然後重新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+死地址
進行Hook
,感覺不直觀,來看一下下面一個被RunAsDate
注入的被掛鉤的函式:
通過上圖,知道為什麼程式碼要這樣構造了吧?
總結與思考
RunAsDate
並不高大上,原理很簡單,其實本質就是用了遠端執行緒注入
+Hook
所有獲取時間函式的API
的方式實現時間的控制。- 有些人利用
RunAsDate
,來破解一些軟體時間使用限制。想要保護自己商業軟體的合法權益,可以通過它的實現原理,檢查是否程式模組有沒有它的Dll
,有的話直接退出程式或者修復Hook
。也可以檢查函式是否被Hook
的方式進行反制。 - 使用
IDA
時,不要過於依賴F5
,雖然直觀方便,但它反編譯出的偽C程式碼並不是完全正確的,甚至是錯誤的,如果想弄對必須進行修改調整。 - 軟體的
Hash
值:EF847F60C02856AB013438D7A55A6CC1
。 - 64位的注入的Dll的IDA分析結果:藍奏雲下載 —— 密碼:f854