文章目錄
- I-介紹
- II-已載入模組的檢測
- III-Hooks,Patches和CRC檢查
- IV-總結
- V-附錄
譯註:這是一篇發表在rootkit.com上的討論怎麼規避warden檢測機制的文章,原文在這裡。作者Darawk是D2的駭客。之前,暴雪通過Module32First/Module32Next對D2 1.11中的外掛進行了第一次打擊,很多使用外掛的玩家尤其是netter's EasyMap的玩家損失慘重。駭客們開始思考Anti-warden的問題,這篇文章就是一些這方面的嘗試。後來,在Darawk的協助下,netter在EasyMap/EasyPlay中實現了Anti-warden。文中提出的幾種方法在EasyMap/EasyPlay的幾個版本中陸續都用到了,可惜這些版本後來還是被抓了。儘管如此,這裡介紹的幾種思路還是很有借鑒意義的,例如用調試寄存器來hook函數在WoW的一些外掛中就得到了應用。
作者在附錄中給出的幾個原始碼檔案的連結地址已經失效,需要的可以從這裡下載。------------------------------------------------I-介紹你們有些人可能已經知道,很多遊戲公司(譯註:就是指暴雪)最近對“駭客”採取了嚴厲措施。他們全方位地實現了基於使用者態(userland)的檢測機制,手段從非常簡單到極其複雜的都有。我在過去幾周裡花了大量的時間,試圖找到一種通用的規避檢測的方法。從某種意義上來說,我們面臨的處境和遊戲公司很像:你只需找到他們的遊戲系統的一個缺點就可以加以利用,而他們必須把系統設計得毫無漏洞;同樣,在我們隱藏自身躲開他們的檢測代碼時,他們只要發現一處失誤就可以抓住我們,而我們要把系統設計、實現得完美無缺。下面的文章是我在這方面的嘗試。它肯定不是完整或者完美的,但我想這是朝著這個目標邁進的第一步。II-已載入模組的檢測
這裡假設我們要往目標(譯註:指遊戲進程)載入一個模組(大部分外掛或者bot的做法)。我們必須對兩種不同的情況加以區分,它們都可能導致惡意模組被檢測出來。這兩種情況是:注入時和其他時間(譯註,指外掛載入時和外掛工作時)。
基本上每種DLL注入法都會在某處調用LoadLibrary。因此如果你想捕捉模組,一種簡單的檢測辦法是截獲LoadLibrary(或者LdrLoadDll,或更底層的Native API)。
這個問題有兩種解決方案。第一種也是最簡單的一種方法是把模組名稱隨機化。由於很多合法軟體往系統裡所有進程注入DLL(Trillian, AIM, 熱鍵軟體,等等),合理的檢測系統不會使用“白名單”設計(這種檢測方法確認只有那些驗證過的模組可以載入,而把那些沒在白名單上列出來的統統看成惡意模組),因此他們只能用黑名單,這就使得隨機化模組名稱是對付這類檢測的完美、可行的解決方案。
我實現的另外一種方法是換一種做法來注入DLL,我稱之為“手工映射”(manual mapping)。第一眼看上去,去類比windows的PE loader是一件令人沮喪的事-你很難把每件事情都做正確。但其實它也沒那麼難,我的ManualMap(附錄)代碼就是幹這個用的。我知道對ManualMap還可以做很多很多改進-事實上我自己有一個改進很大的版本-這個只是一個概念驗證(proof of concept)的東西。
模組被注入到正在啟動並執行進程後,有兩種辦法可以檢測到它。第一種是掃描模組鏈表或者對那些你認為是外掛的模組調用GetModuleHandle(譯註,看GetModuleHandle是否返回NULL)。對付這種檢測的辦法是利用CloakDll(附錄)之類的工具把你的模組從鏈表中去掉。或者用ManualMap,這樣你的模組從一開始就不會被加到鏈表中。我認為第二種做法好一些,但他們差不多是一樣的。第二種檢測已載入模組的方法就聰明多了。它的做法是枚舉系統中的所有記憶體頁面(記憶體頁面是1024位元組對齊的,所以這其實很容易做),然後檢查不良代碼的特徵碼。應對措施是做一個改進的dll loader,把記憶體頁面的邊界到實際資料的起始位移隨機化(譯註,也就是說不把DLL載入在頁面邊界處,而是離邊界有一個隨機的位移)。然而,我覺得更好的方法是建立兩個新的空白頁面來包住你的模組,然後用VirtualProtect給這兩頁設上PAGE_GUARD標誌。標有PAGE_GUARD位的記憶體在訪問時會產生一個異常,你可以用未處理異常過濾器(unhandled exception filter)、向量化異常處理(vectored exception handling)或者KiUserExceptionDispatcher hook(我推薦後者)捕捉這些異常,這樣碰到掃描時你就有機會做一些處理來避免檢測代碼抓到你。III-Hooks,Patches和CRC檢查值得讓人一用的外掛基本上都會以某種方式修改遊戲代碼。但是修改代碼是非常容易被檢測到的,到現在也沒有人能對這個問題提出一個可行、通用的解決方案。我恐怕也不敢號稱我已經完全解決了這個問題,但我發現了一個讓它變得馴服點兒的辦法。我提出過一種不用修改任何目標進程代碼就能hook函數的方法,唯一的缺點是你只能同時hook4個函數。我的做法是利用調試寄存器(即硬體斷點),你可以在附錄的CHook類中看到我的實現。由於調試寄存器檢測起來相當容易,任何人只要用CONTEXT_DEBUG_REGISTERS來調GetThreadContext就可以發現它,隨便一個異常處理常式都會拿到一個包含調試寄存器的上下文結構(context structure),等等。解決辦法是hook住NtGetContextThread、NtSetContextThread和KiUserExceptionDispatcher。當然啦,你得用一些patch來hook這些API。由於頗有一些防病毒/防廣告/防火牆軟體也hook這類函數來“增強系統的整體安全”,所以檢測系統如果僅僅因為你改了這些系統dll就認為你作弊也是不大可能的。這意味著你可以安全的hook這4個函數,用不著擔心被檢測。即使他們也許不會僅僅因為在系統模組中發現了這些hooks就抓你,但它們還是給檢測留下了一點線索-他們可以通過定位你在系統模組中的hooks,分析patch的跳轉指令看它跳到了哪裡,然後對hook處理過程做一下CRC就足以把你認出來。這又有兩種解決辦法。一種是利用int 3斷點指令來做hook,然後捕捉異常(如果看的夠仔細的話你會發現這在我的CHook類裡也實現了)並把異常重新導向到合適的hook處理過程-你需要用標準的jmp patch來hook KiUserExceptionDispatcher(這有點和我們的目標矛盾),或者用兩種標準的SEH(結構化異常處理)形式之一。還有一種辦法,我覺得要好的多,雖然實現起來有點兒困難。其實也沒什麼花頭,就是寫一個hook處理過程的變形引擎,在真正有用的指令之間填充NOP等效指令(NOP等效指令是指mov eax, eax之類的指令。譯註:push eax, pop eax這樣的也是)。這樣,對你的函數做CRC檢查就行不通了。我現在還沒有這麼做,不過以後我可能會寫些PoC代碼(譯註:PoC即Proof of Concept)。最容易的一種做法是從shellcode那裡來的。我敢肯定你們都知道,為了避開IDS簽名(譯註,IDS=Intrusion-detection system),shellcode通常是加密的,帶一個動態解密的loader。用彙編給運行時解密代碼寫一個簡單的變形引擎是很容易的,通過在不同的指令之間放置NOP指令,並且讓密鑰在每次加、解密時隨機變化,你可以把建立簽名的任何企圖變得非常困難-即使不是不可能的話,而且不會犧牲多少效能。調試寄存器在其他很多方面也是相當有用的,你可以用它hook記憶體讀寫和指令讀取,這意味著你可以為記憶體修改做一個回呼函數(用在記憶體資料監測上很理想,這意味著你再也用不著輪詢了)。對調試寄存器的完整描述可以在附錄中找到。另外一點要考慮的是,他們可能都不檢測你的API hooks,簡單的用未經處理資料patch回去就能讓你的hooks無效。這種方法實現起來不難,而且有足夠的可移植性-因為不同的Windows版本中大多數API的開始幾個位元組是不會經常改變的-遊戲公司是有可能這麼乾的。要對付這個問題,我們得意識到一點,就是在對進程映象的程式碼片段進行寫操作之前,必須調用VirtualProtect臨時改變它的頁面屬性。因此,截獲NtProtectVirtualMemory就可以防止他們輕易的覆蓋掉你的辛勤勞動成果。IV-總結總而言之,注入一個模組並設定4個不可檢測(在ring3層級上)的hooks是可能的。也許在某些地方會有漏洞,但我所說的基本原理應該是能到達這個效果的。以上是我對在使用者態建立反檢測系統的所有嘗試。我知道你們肯定有能力在我描述的基礎上加上你們自己的見解,我也真誠的希望你們會這麼做。這個解決方案不像很多正在搞game hacking的人想的那樣是一個秘密的安全體系,也和那些閉源軟體開發商的做法不一樣。遊戲駭客比別人懂得更多,應該知道這一點,因為你們全都是玩逆向工程的。你們活著就是為了擊敗那些通過晦澀的手段來實現的系統安全。。。自己不要掉進同樣的陷阱。V-附錄ManualMap:http://www.darawk.com/Code/ManualMap.cpp 調試寄存器:http://pdos.csail.mit.edu/6.828/2005/readings/i386/s12_02.htm CloakDll:http://www.darawk.com/Code/CloakDll.cpp CHook:
http://www.darawk.com/Code/CHook.h
http://www.darawk.com/Code/CHook.cpp