誰tmd敢說我對技術不感興趣!
http://www.wangrong1002.com/p2.htm
也許是我孤陋寡聞吧,說出來不怕您笑話,對於“變速齒輪”這樣著名的軟件,我一直到五天前,也就是2001年2月28號才第一次聽說。我有幾個同學很喜歡玩圖形MUD,整天見了面就在一起切磋“泥”技。我對MUD本身並沒有多大興趣,但是那天早上偶爾聽他們說某個MUD站點明文規定嚴禁使用“齒輪”,這纔好奇地問他們什麼是“齒輪”。別人告訴我,“齒輪”是一個軟件,能對Windows下的遊戲加速,他們在玩MUD時就依靠這個軟件作弊。這不禁令我一頭霧水,能讓Windows遊戲改變速度,太神奇了! 我一貫對技術很有興趣,聽說有這麼一個神奇的軟件,當然要想想它是怎麼實現的。這個軟件看起來並不複雜,我原以爲一個早自習好好琢磨琢磨就行,可是我想了好幾節課,始終不得其要領。說來也巧,我們這學期有一面必修課是Linux內核原理分析,這幾天正好學到了進程調度,老師說,當一個時鐘中斷髮生的時候,操作系統要做很多事情,比如必要時要重新調度進程從而實現搶先式多任務,還要更新系統時鐘......慢着,我突發奇想,如果讓時鐘中斷產生的更快,會發生什麼事情呢? 我們已經學過“微機原理”這門課程,我知道讓時鐘中斷產生的更快不是難事,以前我就用DOS下的彙編語言寫過這樣的程序,這是我們當時的作業。可是我以前的程序在Windows下雖然可以運行,但並不能對Windows系統加速,道理很顯然:Windows9x是使用x86虛擬機的機制來兼容DOS程序的,我的程序只能改變虛擬機,就是那個黑窗口的時鐘中斷。 於是我試圖把以前的DOS程序搬到32位環境中。用VC內嵌彙編做這件事再合適不過了,在一個VC程序框架中加上一個__asm,然後只管把以前的彙編程序往裏貼就行。我滿懷希望地運行這樣一個拼湊出來的怪物,結果,出現了一個大家都很熟悉的“該程序執行了非法操作”,我的試驗以失敗告終。 後來冷靜下來仔細想想,這次失敗的原因是顯然的。Windows作爲一個複雜的32位操作系統,如果能讓你隨便對硬件進行操作,那也許運行不了幾個程序就崩潰了。但是如何繞過操作系統去操作硬件呢?我首先想到了vxd,編寫一個驅動程序肯定可以操作硬件,但是,很可惜,我不會設計驅動程序。於是我想到了以前看到的CIH的源碼,CIH沒有寫vxd,卻能操作硬件去燒燬BIOS,陳盈豪真是太偉大了,他的程序精巧之處我至今記憶猶新。於是我模仿他的技術,修改IDT表,創建一箇中斷門,然後發生中斷,進入ring0,現在我可以做任何事情了,按照以前的DOS程序那樣,往8253定時器裏寫一個控制字,再分兩次寫入新的時鐘中斷髮生頻率,一切順利!(詳細技術請您參考我的“兄弟變速器”源碼)我看到VC編輯區的光標瘋狂的閃爍;雙擊已經失效了,因爲Windows認爲我雙擊的時間間隔太長;Windows任務欄右方的時間飛快跳動,應該說,我已經成功了。 當時我想當然的以爲“變速齒輪”的原理也是如此,可是當我從同學那裏把“齒輪”拷來並研究時,發現Windows的時鐘並不變快,而遊戲速度照樣可以加上去,也就是說,“齒輪”採用了與我的程序不同的技術,是什麼技術呢?我決定繼續研究。 我訪問了“變速齒輪”的主頁,這個主頁上有一個“你問我答”的欄目,由“齒輪”的作者王榮先生進行技術支持。我試圖在這裏找到一些關於“齒輪”的技術細節,但是很可惜,沒有找到,王榮先生只是告訴大家這個程序不能用VB編寫等等根本連皮毛也不涉及的問題,好不容易見到一個外國人問能不能公佈源代碼,其實這也是我想問的,但是王榮先生明確表示不行,這不禁令我感到非常失望。 我也想過寫信去索取原碼,也許他不向外國人公佈,中國人可不一定。但是咱們“臭老九”最愛一個面子,我實在拉不下臉去問。這時已經是晚上10點了,我決定祭出SoftIce,用一夜時間去研究他的程序。 當時使用的工具是SoftIce,WD32ASM和VC,手邊兩本參考書是《微型計算機系統原理及應用》和《Linux操作系統內核分析》(都是我們的課本,呵呵)。 起初,“變速齒輪”0.2版的一個叫hook.dll的文件很大程度上吸引了我的注意力,我懷疑他使用Windows消息鉤子實現變速,消息鉤子我很熟悉,但我把MSDN上面關於鉤子的介紹看了好久,也沒有想出它和變速有什麼聯繫,這時偶然看了一下在王榮先生的主頁上得到的“變速齒輪”0.1版,才發現老版本中並沒有這個文件,也就是說,我只需要反彙編他的主程序就夠了,於是,二話不說,用WD32ASM先把0.1版的“齒輪”給拆了,彙編代碼5000多行,並不算多。 我是從這個程序的導入函數着手的,以前編程時用於定時的SetTimer,timeGetTime,timeSetEvent等等這裏都導入了,看看它們被引用的地方,我發現這些函數都是集中出現的,而且大都以這樣的形式出現: * Reference To: WINMM.timeGetTime, Ord:0098h :00401F3E 8B0D64424000 mov ecx, dword ptr [00404264] :00401F44 8B11 mov edx, dword ptr [ecx] 也就是說,他並沒有調用這些函數,只是取得了函數的入口地址,保存在ecx中,然後又根據這個入口地址得到了函數的前面幾個字節,保存在edx中。 這讓我想到了前些日子在CSDN上面和別人討論的Hook API的原理,當時我還索取了一份Hook API的例程,如果我要Hook這裏的函數timeGetTime,修改ecx中的地址或者修改edx處的頭幾條指令就行,用匯編語言寫,與上面看到的這段代碼類似。 爲了測試“齒輪”是不是要Hook這裏的timeGetTime,我自己編寫了一個很簡單的小程序,調用timeGetTime,每秒鐘顯示一個數字。用“齒輪”進行加速後,果然顯示的速度快多了。再用SoftIce跟進這個timeGetTime函數,第一條指令變成一個跳轉,這充分說明“齒輪”確實Hook了這幾個API,不難猜測,他要改變函數的返回值,也就是說在timeGetTime結束時還要再跳入“齒輪”自身的代碼,耐心跟下去,我發現回到timeGetTime時棧裏多壓了一個地址,這樣,當timeGetTime用ret指令返回時,先返回“齒輪”的代碼(這個思想確實很巧),返回值經過處理後,才跳回我的應用程序。至於怎麼處理這個返回值就簡單了,改到原先的2倍,應用程序速度也就提高了2倍。 回頭再看WD32ASM反彙編的代碼,我又發現在Hook API前面的不遠處使用了一次SGDT指令和兩次SLDT指令,這是x86保護方式的特有指令,用於獲得全局描述符表,進一步得到局部描述符表,這段代碼引起了我的興趣,用SoftIce跟進去,往下走幾步,一邊跟一邊猜,大致整理出了這樣的思路: 1.創建一個內存映射,把自己的代碼映射到0x80000000以上的地方,在Win9x下,這塊虛存是所有進程共享的。 2.先得到局部描述符表的地址,然後利用這張表修改代碼段的特權級。 3.用局部描述符表創建一個調用門,在x86的保護模式下要進入ring0必須通過門來進行,CIH是用中斷門完成的,這裏用調用門完成,異曲同工。 4.保存幾個關鍵函數前六個字節,改爲一條跳轉指令,跳到自己已經映射到高端的代碼。 5.發生函數調用時進入自己的代碼,通過調用門進入ring0,恢復函數開頭的幾個字節,修改返回值。 這時已經是凌晨5點了,既然主要思想已經掌握,我也就沒有細看這段代碼,8點鐘還要上課,睡覺去也。 回頭想想,我認爲王榮先生的代碼還有幾點值得推敲之處: 1.如果要Hook API,一定要改變函數的第一條指令嗎?如果僅僅改變函數的入口地址,不是既容易編也容易調試嗎? 2.即使要改變函數第一條指令,一定要進入ring0嗎? 3.即使要進入ring0,使用中斷門不是比用調用門更方便嗎? 當然,按照王榮先生在他的主頁上的說法,“變速齒輪”0.1版是他在三年前即1997年寫的,那時Windows95剛剛出來兩年,能有這樣的技術已經難能可貴了,這裏對王榮先生的鑽研精神表示由衷的敬佩。 在我研究出“變速齒輪”的原理後三天,我以自己原先的研究結果爲核心,編寫出了“兄弟變速器”的最初版本,不用“變速齒輪”的技術是因爲我認爲我的技術更優越,何況也沒有拾人牙慧之嫌了 ^_^ 最後再次對王榮先生表示感謝,這樣精彩的創意值得我們敬佩。
“變速齒輪”再研究
作者 BBBKOM
關鍵詞 變速齒輪 調用門 RING0
提起“變速齒輪”(以下簡稱“齒輪”)這個軟件,大家應該都知道吧,該軟件號稱
是全球第一款能改變遊戲速度的程序。我起初用時覺得很神奇,久而久之就不禁思考其實現原理了,但苦於個人水平有限,始終不得其解,成了長駐於腦中揮散不去的大問號。
偶然一天在BBS上看到了一篇名爲《“變速齒輪”研究手記》(以下簡稱《手記》)的文章,我如獲至寶,耐着性子把文章看完了,但之後還是有很多地方不解,不過還是有了比較模糊的認識:原來齒輪是通過截獲遊戲程序對時間相關函數的調用並修改返回結果實現的呀。
爲了徹徹底底地弄清齒輪的原理,我這次打算豁出去了。考慮到《手記》的作者從是研究的“齒輪”的反彙編代碼的,那我也照樣從反彙編代碼開始。不過自認爲彙編功底不夠,又從圖書館借了幾本關於Windows底層機制和386彙編的書,在經過差不多兩週的“修行”之後,自我感覺有點好啦,哈哈,我也有點要迫不及待地把“齒輪”大卸八塊了!
在動手之前,我又把《手記》看了一遍,這次可就清楚多了:通過調用門跳到Ring0級代碼段,修改各系統時間相關函數的前8個字節爲jmp指令,轉跳到“齒輪”映射到2G之上的代碼,達到截獲對各系統時間相關函數的調用的目的。但同時我的疑惑也更明確了:
1.“齒輪”怎樣建立指向自己映射到2G以上內存的代碼的調用門描述符的;
2.“齒輪”怎樣將自己的代碼映射到2G以上線性地址的;
3.映射到2G之上的代碼是怎樣做到在代碼基址更改的情況仍能正確運行的
帶着這樣的疑問,我正式開始了對“齒輪”反彙編代碼的分析。工具嘛,不用說當
然是Softice for Windows98、W32Dasm,OK,出發啦!
我的“齒輪”版本是0.221 for win98和winme的,內含有兩個文件(變速齒輪.exe
和Hook.dll)。先看看Hook.dll裏面有些什麼,用W32Dasm將Hook.dll反彙編,看看它的輸出函數:
?ghWnd@@3PAUHWND__@@A
?gnHotKey1@@3KA
?gnHotKey2@@3KA
?gnHotKey3@@3KA
?gnHotKey4@@3KA
?nHook@@3HA
?SetHook@@YAHPAUHWND__@@@Z
?UnHook@@YAHXZ
看函數名好象該dll只是安裝鉤子捕獲變速熱鍵的,與我的研究目的沒太大的關係, 跳過去!
再看看變速齒輪.exe的導入函數,timeGetTim、GetTickCount等時間相關的函數都
在裏面。嘿,還有CreateFileMappingA和MapViewOfFileEx,看來“齒輪”是用這兩個函
數創建映射文件的。以下列出幾個關鍵的導入函數:
Hook.?gnHotKey1@@3KA
Hook.?gnHotKey2@@3KA
Hook.?gnHotKey3@@3KA
Hook.?gnHotKey4@@3KA
Hook.?SetHook@@YAHPAUHWND__@@@Z
KERNEL32.CreateFileMappingA
KERNEL32.GetModuleFileNameA
KERNEL32.GetModuleHandleA
KERNEL32.GetTickCount
KERNEL32.MapViewOfFileEx
KERNEL32.QueryPerformanceCounte
USER32.KillTimer
USER32.SendMessageA
USER32.SetTimer
WINMM.timeGetTime
WINMM.timeSetEvent
既然“齒輪”截獲了timeGetTime,那我就跟蹤timeGetTime函數的執行情況。
我先寫了個Win32 APP (以下簡稱APP),當左擊客戶區時會調用timeGetTime並將返回的結果輸出至客戶區。運行這個程序,打開“齒輪”,改變當前速度。
Ctrl + D 呼出Softice,bpx timeGetTime ,退出,再左擊APP客戶區,Softice跳
出。哈,果然timeGetTime函數的首指令成了jmp 8xxx 002A ,好F8繼續執行,進入了“ 齒輪”映射到2G線性地址之上的代碼。一路F8下去,發現接着“齒輪”把timeGetTime 首指令恢復,並再次調用timeGetTime,這樣就得到了timeGetTime的正確結果,保存結果。“齒輪”再把timeGetTime首指令又改爲jmp 8xxx 002A 。接下來都猜得到“齒輪”要幹什麼了!沒錯,將得到的返回值修改後返回至調用timeGetTime的程序APP。
我仔細分析了一下,“齒輪”修改返回值的公式如下:
倍數*(返回值-第一次調用timeGetTime的返回值)
修改後的返回值=---------------------------------------------------+上一次修改後的返回值
100000
公式中“上次修改後的返回值”是自己猜測的未經證實,僅供參考。
代碼分析已經進行一部分了,可我之前的疑問仍未解決,“齒輪”是怎麼將代碼映
射的?又是怎麼得到修改代碼的權限的?
既然“齒輪”中調用了CreateFileMappingA,我想其安裝調用門,映射代碼的初始
化部分應該就在調用該函數代碼的附近。好,沿着這個思路,呼出Softice,在CreateF ileMappingA處設置斷點,將“齒輪”關閉後再運行。Softice跳出,停在了CreateFile MappingA處,F11回到“齒輪”的代碼。看到了“齒輪”調用CreateFileMappingA的形式
如下:
CreateFileMappingA(FF,0,4,0,10000,0);
可見“齒輪”創建了長度爲0x10000的映射文件,繼續,“齒輪”接着又調用
MapViewOfFileEx,調用形式如下:
MapViewOfFileEx(EDX,2,0,0,0,EAX);
//EDX爲CreateFileMappingA返回的映射文件句柄
//EAX爲申請映射代碼的基址,第一次調用時EAX爲0x8000 0000
這裏就是關鍵了,“齒輪”要將映射文件映射至基址爲0x8000 0000 的內存空間中,可並不見得Windows就真的允許其映射呀?果然,“齒輪”在在調用之後判斷返回值是否有效,無效則將上次申請的基址加上0x1000,再次調用MapViewOfFileEx,一直循環到成功爲止,再將返回的地址保存。
接下來“齒輪”將原“齒輪”exe中的截獲API的代碼逐字節拷貝到映射區域去。至
此,“齒輪”已經將關鍵代碼映射到2G以上線性地址中了。
我再F8,哈哈,和熟悉的SGDT指令打了個照面。“齒輪”保存全局描述符表線性基 址,再用SLDT指令保存局部描述符表索引,計算出LDT基址。接着呢“齒輪”在局部描述表中創建了一個特權等級爲0的代碼段指向需要利用Ring0特權修改代碼的“齒輪”自己的代碼,並把局部描述表中索引爲2的調用門指向的地址改爲“齒輪”映射到高於2G的代碼。
然後“齒輪”依次調用各時間相關的API,保存其返回值留做計算返回時結果用。
“齒輪”又依次調用映射到高於2G的代碼修改各API的首指令。到了這裏,“齒輪”的初
始化部分就結束了,只等着還矇在鼓裏的遊戲上鉤啦,哈哈!
結束代碼只不過是作些恢復工作罷了,僅僅是初始化代碼的逆過程,所以就不再
贅述(其實是我自己懶得看了,^_^!).
至此,我對“齒輪”的加速原理已有大致的瞭解,深刻感受到“齒輪”代碼的精巧, 所以覺得有必要將"齒輪"中所運用到的一些技巧作一個總結:
1.基址無關代碼的編寫
姑且以上面一句話作標題,^_^。看了“齒輪”的初始化代碼,知道其映射代碼
的基址差不多是隨機的,那麼“齒輪”是怎麼保證映射後的代碼能正常運行的呢?如果 代碼是完全順序執行的倒沒什麼問題,但如果要調用自己映射代碼中的子程序呢?呵呵,就只有運行時計算出子程序的入口地址並調用了,不過還是要先得到映射代碼所在的地址纔行。“齒輪”簡單地用兩條指令就得到當前正在執行的指令的地址,具體如下(地址爲假設的):
0:0 call 5
0:5 pop esi
現在esi中的值就是5了,哈哈!
這裏的call用的是近調用,整條指令爲E800000000,即爲調用下一條指令.所進行
的操作只不過是把下一條指令的地址入棧而已.再pop將返回地址(即pop指令本身的地址)取出.
2.修改調用門,生成jmp指令,修改代碼
這些都是高度依賴於CPU的操作,技巧性也很強,主要是鑽了操作系統的漏洞。比如“齒輪”就是用SGDT,SLDT獲得全局和局部描述符表基址來安裝調用門,通過訪問調用門來獲取RING0權限作一些平時不爲系統所允許的操作;而CIH病毒是用SIDT獲得中斷描述符表基址安裝中斷門然後出發軟中斷獲取RING0權限的,原理都是一樣的。這些在水木上討論過很多遍,大家都很熟悉,所以也就不敢班門弄斧,寫到此爲止。
3.64K代碼編寫
由調用CreateFileMappingA函數參數可知“齒輪”只映射10000(64K)大小的
區域,所以其映射在2G之上的代碼和數據決不能大於64K。我想作者之所以選擇64K爲映射區域的大小,可能是與調用子程序或數據時容易計算地址有關。在映射代碼的任意一處得到當前指令地址之後將其低16位置0即可得到映射代碼的基地址,再加上子程序入口或數據的偏移即可求得其絕對地址。
我的評論:
一句話:佩服“齒輪”的作者王榮先生。
“齒輪”的代碼表現他對windows運行機制的深刻理解以及深厚的彙編功底還有豐
富的想象力。對我來說“齒輪”彷彿就是一件精美的藝術品,每個細處都很值得玩味一 番,所以我纔在看過“齒輪”代碼之後有了把我的分析過程用筆寫下來的衝動。但同時 我又不得不承認“齒輪”的功能的實現是依靠其高度技巧化的代碼實現的,換句話說就 是這種的方法侷限性實在是太大了。不就是截獲API嘛,用的着這麼麻煩嗎?
爲了證實自己的想法,我在Codeguru上直接找了個HOOK API 的代碼,該代碼是通過安裝WH_CBT類型全局鉤子在所有被插入DLL的進程中修改進程PE映像的輸入節達到截獲API的(這種方法在《windows核心編程》中有詳細說明)。把代碼稍做修改,就能工作了(在星際爭霸下試過,可以改變遊戲速度)。儘管只在98下試過,但我覺得肯定也能在2000下用,因爲代碼中只用了一兩句彙編指令,而且整個程序都是在RING3下運行的,沒有作出什麼出軌的舉動。當然這種方法也有缺點,就是對用Loadlibrary加載WINMM.dll再用GetProcAddress獲取timeGetTime地址的API調用不起作用(原因在《windows核心編程》中有說明)。
我打算在將測試用程序稍稍完善後再公佈源代碼,屆時歡迎大家下載。
我的感謝:
在我徹底弄清“齒輪”的代碼之後,已經是第三天的上午了,無奈自己才疏學淺,
全不像《手記》的作者只花了一個晚上就弄清楚,我可是花了一個上午、兩個下午、兩個晚上才結束了戰鬥,實在是慚愧呀。
自己之所以能自得其樂地堅持了兩天多,是與寢室兄弟小強的支持分不開的。窮 困潦倒的我在這幾天不知道總共抽了他多少支菸,無以爲報,只有在這裏說一聲謝謝了!另外還要感謝sunlie非常地閱讀本文,指出了原文中的錯誤並提出了非常寶貴的意見!
最後要說的就是個人水平有限,文中難免出現錯誤,歡迎大家討論!^_^
附A:
使用工具:Softice for Windows98,W32Dasm,VisualC++ 6.0
操作系統:Window98 2nd
分析目標:變速齒輪 for 98me 版本:0.221
參考書籍或文章:
80x86彙編語言程序設計教程 楊季文等編著 清華大學出版社
windows剖析--初始化篇及內核篇 清華大學出版社
虛擬設備驅動程序開發
intel 32位系統軟件編程
80x86指令參考手冊
《“變速齒輪”研究手記》
附B:
“齒輪”關鍵代碼完全註釋
一、初始化部分(從"齒輪"調用CreateFileMappingA函數開始分析)
0167:00401B0E PUSH 00
0167:00401B10 PUSH 00010000
0167:00401B15 PUSH 00
0167:00401B17 PUSH 04
0167:00401B19 PUSH 00
0167:00401B1B PUSH FF
0167:00401B1D CALL [KERNEL32!CreateFileMappingA]
;調用CreateFileMappingA
; 調用形式如右:CreateFileMappingA(FF,0,4,0,10000,0)
0167:00401B23 MOV ECX,[EBP-30]
0167:00401B26 MOV [ECX+00000368],EAX
0167:00401B2C MOV DWORD PTR [EBP-14],80000000
0167:00401B33 JMP 00401B41
0167:00401B35 MOV EDX,[EBP-14]
0167:00401B38 ADD EDX,00010000
;申請基址加0x10000
0167:00401B3E MOV [EBP-14],EDX
0167:00401B41 MOV EAX,[EBP-14]
0167:00401B44 PUSH EAX ;映射文件基址
0167:00401B45 PUSH 00 ;映射的字節數
0167:00401B47 PUSH 00 ;文件偏移低32位
0167:00401B49 PUSH 00 ;文件偏移高32位
0167:00401B4B PUSH 02 ;訪問模式
0167:00401B4D MOV ECX,[EBP-30]
0167:00401B50 MOV EDX,[ECX+00000368]
0167:00401B56 PUSH EDX
;CreateFileMappingA返回的映射文件句柄
0167:00401B57 CALL [KERNEL32!MapViewOfFileEx]
; 調用形式如右:MapViewOfFileEx(EDX,2,0,0,0,EAX)
0167:00401B5D MOV ECX,[EBP-30]
;[EBP-30]爲即將映射到2G之上
0167:00401B60 MOV [ECX+0000036C],EAX
; 的代碼的數據域的起始地址
0167:00401B66 MOV EDX,[EBP-30]
0167:00401B69 CMP DWORD PTR [EDX+0000036C],00
;檢查MapViewOfFileEx
0167:00401B70 JZ 00401B74
;返回值,若爲0則繼續調
0167:00401B72 JMP 00401B76 ;調用MapViewOfFileEx
0167:00401B74 JMP 00401B35 ;直至成功爲止
0167:00401B76 MOV EAX,[EBP-30]
0167:00401B79 MOV ECX,[EAX+0000036C]
0167:00401B7F MOV [EBP-08],ECX
;映射文件起始地址存入[EBP-08]
0167:00401B82 CALL [WINMM!timeGetTime]
0167:00401B88 MOV [EBP-14],EAX
;將初次調用timeGetTime
0167:00401BA0 MOV ECX,[EBP-08]
;的返回值保存到[EBP-14]
0167:00401BA3 MOV EDX,[EBP-14]
;以及映射文件基址+FF30處
0167:00401BA6 MOV [ECX+0000FF30],EDX
...省略的代碼類似的保存調用初次GetTickCount,QueryPerformanceCounter的返回值
0167:00401BED MOV DWORD PTR [EBP-14],00000000
0167:00401BF4 MOV EDX,[EBP-30]
0167:00401BF7 MOV EAX,[EDX+0000036C]
0167:00401BFD MOV ECX,[EBP-14]
0167:00401C00 MOV BYTE PTR [ECX+EAX+0000F000],9A
;9a爲遠調用的指令碼
0167:00401C08 MOV EDX,[EBP-14]
0167:00401C0B ADD EDX,01
0167:00401C0E MOV [EBP-14],EDX
0167:00401C11 MOV EAX,[EBP-14]
0167:00401C14 ADD EAX,04
0167:00401C17 MOV [EBP-14],EAX
0167:00401C1A MOV ECX,[EBP-30]
0167:00401C1D MOV EDX,[ECX+0000036C]
0167:00401C23 MOV EAX,[EBP-14]
0167:00401C26 MOV BYTE PTR [EAX+EDX+0000F000],14
;14爲調用門描述符的索引
0167:00401C2E MOV ECX,[EBP-14]
0167:00401C31 ADD ECX,01
0167:00401C34 MOV [EBP-14],ECX
0167:00401C37 MOV EDX,[EBP-30]
0167:00401C3A MOV EAX,[EDX+0000036C]
0167:00401C40 MOV ECX,[EBP-14]
0167:00401C43 MOV BYTE PTR [ECX+EAX+0000F000],00
;CALL指令其他部分
0167:00401C4B MOV EDX,[EBP-14]
0167:00401C4E ADD EDX,01
0167:00401C51 MOV [EBP-14],EDX
0167:00401C54 MOV EAX,[EBP-30]
0167:00401C57 MOV ECX,[EAX+0000036C]
0167:00401C5D MOV EDX,[EBP-14]
0167:00401C60 MOV BYTE PTR [EDX+ECX+0000F000],C2
0167:00401C68 MOV EAX,[EBP-14]
0167:00401C6B ADD EAX,01
0167:00401C6E MOV [EBP-14],EAX
0167:00401C71 MOV ECX,[EBP-30]
0167:00401C74 MOV EDX,[ECX+0000036C]
0167:00401C7A MOV EAX,[EBP-14]
0167:00401C7D MOV BYTE PTR [EAX+EDX+0000F000],00
0167:00401C85 MOV ECX,[EBP-14]
0167:00401C88 ADD ECX,01
0167:00401C8B MOV [EBP-14],ECX
0167:00401C8E MOV EDX,[EBP-30]
0167:00401C91 MOV EAX,[EDX+0000036C]
0167:00401C97 MOV ECX,[EBP-14]
0167:00401C9A MOV BYTE PTR [ECX+EAX+0000F000],00
0167:00401CA2 MOV EDX,[EBP-14]
;以上代碼爲在映射代碼偏移F000處寫入指令CALL 0014:0000
0167:00401CA5 ADD EDX,01
;指令 A91400C20000共6個字節
0167:00401CA8 MOV [EBP-14],EDX ;
0167:00401CAB MOV ESI,0040213B
;要複製的代碼的起始地址
0167:00401CB0 MOV EDI,[EBP-08]
;要複製代碼的目標地址(映射區域中)
0167:00401CB3 MOV ECX,00402688
;402688爲要複製的代碼的末地址
0167:00401CB8 SUB ECX,ESI
0167:00401CBA REPZ MOVSB ;將代碼全部複製到映射區域
0167:00401CBC SGDT FWORD PTR [EBP-1C] ;這句開始就很關鍵了
0167:00401CC0 LEA EAX,[EBP-001C]
0167:00401CC6 MOV EAX,[EAX+02] ;取GDT線性基址
0167:00401CC9 XOR EBX,EBX
0167:00401CCB SLDT BX ;取LDT在GDT中的偏移
0167:00401CCE AND BX,-08
0167:00401CD2 ADD EAX,EBX
0167:00401CD4 MOV ECX,[EAX+02]
0167:00401CD7 SHL ECX,08
0167:00401CDA MOV CL,[EAX+07]
0167:00401CDD ROR ECX,08 ;以上計算出LDT線性基址
0167:00401CE0 MOV [EBP-0C],ECX ;保存
0167:00401CE3 MOV EAX,[EBP-30]
0167:00401CE6 MOV ECX,[EBP-0C]
0167:00401CE9 MOV [EAX+00000370],ECX
0167:00401CEF MOV EDX,[EBP-30]
0167:00401CF2 MOV EAX,[EDX+0000036C]
0167:00401CF8 MOV ECX,[EBP-0C]
0167:00401CFB MOV [EAX+0000FE00],ECX
;將LDT線性基址保存至映射代碼中
0167:00401D01 MOV AX,CS
;得到當前代碼段描述符號
0167:00401D04 AND AX,FFF8
0167:00401D08 MOV [EBP-10],AX
0167:00401D0C MOV EDX,[EBP-10]
0167:00401D0F AND EDX,0000FFFF
;EDX爲代碼段描述符在LDT中的偏移量
0167:00401D15 MOV EAX,[EBP-30]
0167:00401D18 MOV ECX,[EAX+00000370] ;ECX此時爲LDT線性基址 0167:00401D1E MOV EAX,[EBP-30]
0167:00401D21 MOV EAX,[EAX+00000370]
;EAX此時爲LDT線性基址
0167:00401D27 MOV ESI,[EDX+ECX]
0167:00401D2A MOV [EAX+08],ESI
0167:00401D2D MOV ECX,[EDX+ECX+04]
;以上將當前代碼段描述符複製到
0167:00401D31 MOV [EAX+0C],ECX ;LDT第1項
0167:00401D34 MOV EDX,[EBP-30]
0167:00401D37 MOV EAX,[EDX+00000370]
0167:00401D3D MOV CL,[EAX+0D]
0167:00401D40 AND CL,9F
0167:00401D43 MOV EDX,[EBP-30]
0167:00401D46 MOV EAX,[EDX+00000370]
0167:00401D4C MOV [EAX+0D],CL
;以上修改LDT第1項的DPL爲0,則當由調用門轉到該段代碼時即獲得RING0權限
0167:00401D4F MOV EAX,[EBP-0C]
0167:00401D52 ADD EAX,10 ;獲得LDT中索引爲2的調用門地址
0167:00401D55 MOV EBX,0040213B
0167:00401D5A MOV [EAX],EBX
0167:00401D5C MOV [EAX+04],EBX
0167:00401D5F MOV WORD PTR [EAX+02],000C
0167:00401D65 MOV WORD PTR [EAX+04],EC00 ;調用門修改完畢
0167:00401D6B MOV ECX,[EBP-08]
0167:00401D6E MOV EDX,[WINMM!timeGetTime]
0167:00401D74 MOV [ECX+0000FEE0]
;EDX;保存timeGetTime入口地址
...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,SetTimer,
timeGetSystemTime,QueryPerformanceCounter入口地址
0167:00401DD2 MOV ECX,[EBP-08]
0167:00401DD5 MOV EAX,[WINMM!timeGetTime]
0167:00401DDA MOV EBX,[EAX]
0167:00401DDC MOV [ECX+0000FE40],EBX
0167:00401DE2 MOV EBX,[EAX+04]
0167:00401DE5 MOV [ECX+0000FE44],EBX
;保存timeGetTime函數前8個字節指令
...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,
timeGetSystemTime , QueryPerformanceCounter前8個字節指令
0167:00401E6D MOV BYTE PTR [ECX+0000FE90],E9
0167:00401E74 MOV EAX,00402165
0167:00401E79 SUB EAX,0040213B
;EAX爲截獲代碼在映射代碼中的偏移
0167:00401E7E ADD EAX,ECX ;計算出截獲代碼的線性入口地址
0167:00401E80 SUB EAX,[WINMM!timeGetTime]
0167:00401E86 SUB EAX,05 ;JMP指令總長5個字節
0167:00401E89 MOV [ECX+0000FE91],EAX
;計算生成從timeGetTime跳到截獲代碼的JMP指令並保存
...省略部分依次計算並生成GetTickCount,GetMessageTime,timeSetEvent,
timeGetSystemTime , QueryPerformanceCounter跳到截獲代碼的JMP指令
並保存
0167:00401F58 CLI ;關閉中斷,謹防修改代碼時發生意外
0167:00401F59 MOV EAX,004021F3 ;
0167:00401F5E SUB EAX,0040213B;計算子程序在映射代碼中的偏移
0167:00401F63 ADD EAX,[EBP-08] ;EAX=8xxx 00B8
0167:00401F66 PUSH EAX ;傳入參數EAX爲修改timeGetTime代碼的
;子程序入口地址
0167:00401F67 MOV EAX,[EBP-08] ;調用8xxx 0000
0167:00401F6A CALL EAX ;返回時timeGetTime首指令被更改
...省略部分依次修改GetTickCount,GetMessageTime,timeSetEvent,
timeGetSystemTime , QueryPerformanceCounter函數的首指令
0167:00401FF SETI ;設置中斷,初始化代碼結束
二、截獲時間函數部分(以timeGetTime爲例子,代碼以跟蹤順序列出)
timeGetTime
JMP 832A 002A
;這是timeGetTime被修改後的首指令
0167:832A 002A CLI
;此時[esp]=40BF2C,即遊戲程序中調用timeGetTime函數的下一條指令
...(6個)各寄存器分別入棧 且MOV EBP,ESP
0167:832A 0033 CALL 832A 0038
;將當前EIP入棧(即下一條指令的地址)
0167:832A 0038 POP EDI ;取出當前指令地址
XOR DI , DI
MOV ESI , EDI
;將64K內存首地址賦給ESI
;此時ESI=EDI=832A 0000
ADD ESI , 0040 2102
SUB ESI , 0040 213B ;求出映射代碼首地址
PUSH ESI
0167:832A 004B CALL EDI ;ESI爲傳進的參數
;返回時已經將timeGetTime代碼還原
0167:832A 004D CALL 832A 0052 ;
0167:832A 0052 POP EDI
XOR DI ,DI ;故技重施
CALL [EDI + 0000FEED];調用原timeGetTime函數
SUB EAX,[EDI + 0000 FF30]
;減去第一次調用timeGetTime的結果
MUL DWORD PTR [EDI+0000 FE30]
;乘以用戶所指定的倍數
MOV EBX ,00100000
DIV EBX
;除以常數100000
ADD EAX ,[EDI+ 0000FE20]
MOV EAX,004021F3
SUB EAX,0040213B
ADD EAX,EDI
;以上指令爲修改timeGetTime函數返回值
PUSH EAX
;EAX爲傳進的參數
CALL EDI
;返回時又將timeGetTime首指令換成JMP
...恢復各寄存器的值,EAX中爲修改後的返回值
RET ;此時[ESP]=40BF2C,執行RET將返回到遊戲中去
;
0167:832A 0000 CALL 832A 0005
0167:832A 0005 POP EDI
XOR DI ,DI ;老套了撒^_^
MOV ESI ,[EDI+0000 FE00]
;此地址保存着LDT的線性基址
MOV EAX,[ESP+04]
MOV [ESI +10],AX
SHR EAX,10
MOV [ESI+16],AX
;以上代碼將LDT中索引爲2的調用門描述符的偏移改爲傳入的參數
...
MOV EAX,0000 0F00
CALL EAX
;調用子程序修改timeGetTime代碼
0167:832A 0027 RET 4
;彈出參數,返回
;
0167:832A F000 CALL 0014:00000000
RET 0
;
000C:832A 0097 CALL 832A 009C
000C:832A 009C POP EDI
MOV EAX,[EDI+0000 FE40]
MOV EBX,[EDI+0000 FEE0]
MOV [EBX],EAX
MOV EAX,[EDI+0000 FE44]
MOV [EBX+04],EAX
RETF
注:EDI+0000 FE40起前8個字節爲原timeGetTime函數的指令
EDI+0000 FEE0保存着timeGetTime函數的入口地址
以上即恢復timeGetTime前8個字節的代碼
;
000C:832A 00B8 CALL 832A 00BD
000C:832A 00BD POP EDI
XOR DI ,DI
...
MOV EAX,[EDI+0000 FE90]
MOV EBX,[EDI+0000 FEE0]
MOV [EBX],EAX
MOV EAX,[EDI+0000FE94]
MOV [EBX+04],EAX
RETF
注:EDI+0000 FE90 起前8個字節保存着JMP 832A 002A 指令
是由“齒輪”初始化部分代碼計算出來的,以上代碼將JMP 832A 002A
寫入timeGetTime函數
關於soft-ice:
簡介
SoftICE是Compuware NuMega公司[1]的產品,是目前公認最好的系統級調試工具!兼容性和穩定性極好,可在源代碼級調試各種應用程序和設備驅動程序,也可使用TCP/IP連接進行遠程調試。ICE的含義 ICE(In Circuit Emulator)即實體電路模擬器,是用來跟蹤軟件執行動作細節的一個模擬CPU的電子設備。當然這種設備價格昂貴,不是常人所能擁有的。NuMega公司推出的Soft "ICE",意思是靠軟件實現ICE的功能。 SoftICE單獨發行的最高版本是SoftICE v4.3.2.2485,針對不同平臺推出的相應的版本:DOS,Windows3.x,Windows 9x,Windows NT和Windows 2000。
初級使用法
由於某些原因,需要用到SoftICE調試工具,期間經歷了不少痛苦的歷程.打算寫出來,也算做個記錄吧. 我下載的Driver Studio 3.2.如果藍屏或者你的鼠標會動不了,鍵盤卡住不動,請下載補丁.還有一種情況比較特殊我遇到的:鍵盤是PS/2 接口,而鼠標是USB接口,這時候似乎沒其他的辦法,只有花錢讓接口統一了. SoftICE第一次調試程序 當初第一次調試程序時,網上查了無數的資料,還是不得其解. 後來還是看USING SOFTICE.PDF看來的. 1.先打開softice,後打開symbol loader 軟件.默認路徑如下: [開始]->[所有程序]->[compuware DriverStudio]->[debug]->[start softice] [開始]->[所有程序]->[compuware DriverStudio]->[debug]->[symbol loader] 2.在Symbol loader中 [FILE]->[OPEN].打開生成的文件.一般是.exe或者.dll吧. 3.然後[Module]->[Translate]這步是爲了把.pdb軟件轉換成.nms文件..nms文件是 SoftICE特有的調試文件. 4.最後[Module]->[Load]把.nms加載. 這時候SoftICE會自動攔截到main入口點. 那如何確定你確實加載了symbol呢?用File指令.如下: File * 然後SoftICE會列出已經加載的符號表.如果沒加載.請重複Symbol loader[2-4]操作. 這時候你是不是很開心了呢?哈哈,別高興太早了. 你在SoftICE命令窗口中輸入 bpx ntdll!ZwRaiseException 會出現 (Symbol not defined ). 是不是崩潰 了?如果不能下內核API,我用SoftICE幹什麼!OD多好使啊.雖然OD用得不多. 不過喜歡OD可以邊聽歌,邊幹活. 其實解決辦法很簡單. 在C:/WINDOWS/system32/drivers目錄下修改Winice.dat.(即去掉分號). 修改後文件如下: NMI=ON VERBOSE=ON HST=10240 DRAWSIZE=10240 INIT="X; width 160;lines 70;set font 2;wl;wt;wd;ws;" SYM=512 DISASSEMBLYHINTS=ON LOWERCASE=OFF CODEMODE=OFF SELECTORS=ON CHECKSTRINGS=ON AUTOCONNECT=OFF NETSUPPORT=OFF HOSTNAME=MICROSOF-47742B F1="h;" F2="^wr;" F3="^src;" F4="^rs;" F5="^x;" F6="^ec;" F7="^here;" F8="^t;" F9="^bpx;" F10="^p;" F11="^G *SS:ESP;" F12="^p ret;" SF3="^format;" AF1="^wr;" AF2="^wd;" AF3="^wc;" AF4="^ww;" AF5="CLS;" AF11="^dd dataaddr->0;" AF12="^dd dataaddr->4;" CF1="altscr off; lines 60; wc 32; wd 8;" CF2="^wr;^wd;^wc;" MACROS=32 MOUSE=ON ECHOKEYS=OFF NOLEDS=OFF NOPAGE=OFF PENTIUM=ON THREADP=ON SIWVIDRANGE=ON MENU=Copy , NMPD_COPY , 0 MENU=Paste , NMPD_PASTE , 0 MENU=Copy&Paste , NMPD_COPYANDPASTE , 0 MENU=Display , NMPD_DISPLAY , 0 MENU=Un-Assemble , NMPD_UNASSEMBLE , 0 MENU=What , NMPD_WHAT , 0 MENU=Prev , NMPD_PREV , 0 MENU=Reip , r eip %cp% , 0 MENU=Add Watch , watch %cp% , 0 MENU=Break On Text , bpx %cp% , 0 MENU=Name , name %cp% , 4 ; WINICE.DAT ; (SystemRoot/System32/Drivers/WINICE.DAT) ; for use with SoftICE for Windows NT (versions 3.0 and greater) ; ; ***** Examples of export symbols that can be included ***** ; Change the path to the appropriate drive and directory (下面的分號全去了,;代表註釋) EXP=C:/WINDOWS/System32/hal.dll EXP=C:/WINDOWS/System32/ntoskrnl.exe EXP=C:/WINDOWS/System32/ntdll.dll EXP=C:/WINDOWS/System32/kernel32.dll EXP=C:/WINDOWS/System32/user32.dll EXP=C:/WINDOWS/System32/csrsrv.dll EXP=C:/WINDOWS/System32/basesrv.dll EXP=C:/WINDOWS/System32/winsrv.dll