“變速齒輪”再研究
提起“變速齒輪”(以下簡稱“齒輪”)這個軟件,大家應該都知道吧,該軟件號稱
是全球第一款能改變游戲速度的程序。我起初用時覺得很神奇,久而久之就不禁思考其實現原理了,但苦于個人水平有限,始終不得其解,成了長駐于腦中揮散不去的大問號。
偶然一天在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反匯編,看看它的輸出函數:
__@@A">?ghWnd@@3PAUHWND__@@A
?gnHotKey1@@3KA
?gnHotKey2@@3KA
?gnHotKey3@@3KA
?gnHotKey4@@3KA
?nHook@@3HA
__@@@Z">?SetHook@@YAHPAUHWND__@@@Z
?UnHook@@YAHXZ
看函數名好象該dll只是安裝鉤子捕獲變速熱鍵的,與我的研究目的沒太大的關系, 跳過去!
再看看變速齒輪.exe的導入函數,timeGetTim、GetTickCount等時間相關的函數都
在里面。嘿,還有CreateFileMappingA和MapViewOfFileEx,看來“齒輪”是用這兩個函
數創建映射文件的。以下列出幾個關鍵的導入函數:
Hook.?gnHotKey1@@3KA
Hook.?gnHotKey2@@3KA
Hook.?gnHotKey3@@3KA
Hook.?gnHotKey4@@3KA
__@@@Z">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:00401B0EPUSH00
0167:00401B10PUSH00010000
0167:00401B15PUSH00
0167:00401B17PUSH04
0167:00401B19PUSH00
0167:00401B1BPUSHFF
0167:00401B1DCALL[KERNEL32!CreateFileMappingA]
;調用CreateFileMappingA
;調用形式如右:CreateFileMappingA(FF,0,4,0,10000,0)
0167:00401B23MOV ECX,[EBP-30]
0167:00401B26MOV [ECX+00000368],EAX
0167:00401B2CMOV DWORD PTR [EBP-14],80000000
0167:00401B33JMP 00401B41
0167:00401B35MOV EDX,[EBP-14]
0167:00401B38ADD EDX,00010000
;申請基址加0x10000
0167:00401B3EMOV [EBP-14],EDX
0167:00401B41MOV EAX,[EBP-14]
0167:00401B44PUSHEAX;映射文件基址
0167:00401B45PUSH00 ;映射的字節數
0167:00401B47PUSH00 ;文件偏移低32位
0167:00401B49PUSH00 ;文件偏移高32位
0167:00401B4BPUSH02 ;訪問模式
0167:00401B4DMOV ECX,[EBP-30]
0167:00401B50MOV EDX,[ECX+00000368]
0167:00401B56PUSHEDX
;CreateFileMappingA返回的映射文件句柄
0167:00401B57CALL[KERNEL32!MapViewOfFileEx]
; 調用形式如右:MapViewOfFileEx(EDX,2,0,0,0,EAX)
0167:00401B5DMOV ECX,[EBP-30]
;[EBP-30]為即將映射到2G之上
0167:00401B60MOV [ECX+0000036C],EAX
; 的代碼的數據域的起始地址
0167:00401B66MOV EDX,[EBP-30]
0167:00401B69CMP DWORD PTR [EDX+0000036C],00
;檢查MapViewOfFileEx
0167:00401B70JZ00401B74
;返回值,若為0則繼續調
0167:00401B72JMP 00401B76 ;調用MapViewOfFileEx
0167:00401B74JMP 00401B35 ;直至成功為止
0167:00401B76MOV EAX,[EBP-30]
0167:00401B79MOV ECX,[EAX+0000036C]
0167:00401B7FMOV [EBP-08],ECX
;映射文件起始地址存入[EBP-08]
0167:00401B82CALL[WINMM!timeGetTime]
0167:00401B88MOV [EBP-14],EAX
;將初次調用timeGetTime
0167:00401BA0MOV ECX,[EBP-08]
;的返回值保存到[EBP-14]
0167:00401BA3MOV EDX,[EBP-14]
;以及映射文件基址+FF30處
0167:00401BA6MOV [ECX+0000FF30],EDX
...省略的代碼類似的保存調用初次GetTickCount,QueryPerformanceCounter的返回值
0167:00401BEDMOV DWORD PTR [EBP-14],00000000
0167:00401BF4MOV EDX,[EBP-30]
0167:00401BF7MOV EAX,[EDX+0000036C]
0167:00401BFDMOV ECX,[EBP-14]
0167:00401C00MOV BYTE PTR [ECX+EAX+0000F000],9A
;9a為遠調用的指令碼
0167:00401C08MOV EDX,[EBP-14]
0167:00401C0BADD EDX,01
0167:00401C0EMOV [EBP-14],EDX
0167:00401C11MOV EAX,[EBP-14]
0167:00401C14ADD EAX,04
0167:00401C17MOV [EBP-14],EAX
0167:00401C1AMOV ECX,[EBP-30]
0167:00401C1DMOV EDX,[ECX+0000036C]
0167:00401C23MOV EAX,[EBP-14]
0167:00401C26MOV BYTE PTR [EAX+EDX+0000F000],14
;14為調用門描述符的索引
0167:00401C2EMOV ECX,[EBP-14]
0167:00401C31ADD ECX,01
0167:00401C34MOV [EBP-14],ECX
0167:00401C37MOV EDX,[EBP-30]
0167:00401C3AMOV EAX,[EDX+0000036C]
0167:00401C40MOV ECX,[EBP-14]
0167:00401C43MOV BYTE PTR [ECX+EAX+0000F000],00
;CALL指令其他部分
0167:00401C4BMOV EDX,[EBP-14]
0167:00401C4EADD EDX,01
0167:00401C51MOV [EBP-14],EDX
0167:00401C54MOV EAX,[EBP-30]
0167:00401C57MOV ECX,[EAX+0000036C]
0167:00401C5DMOV EDX,[EBP-14]
0167:00401C60MOV BYTE PTR [EDX+ECX+0000F000],C2
0167:00401C68MOV EAX,[EBP-14]
0167:00401C6BADD EAX,01
0167:00401C6EMOV [EBP-14],EAX
0167:00401C71MOV ECX,[EBP-30]
0167:00401C74MOV EDX,[ECX+0000036C]
0167:00401C7AMOV EAX,[EBP-14]
0167:00401C7DMOV BYTE PTR [EAX+EDX+0000F000],00
0167:00401C85MOV ECX,[EBP-14]
0167:00401C88ADD ECX,01
0167:00401C8BMOV [EBP-14],ECX
0167:00401C8EMOV EDX,[EBP-30]
0167:00401C91MOV EAX,[EDX+0000036C]
0167:00401C97MOV ECX,[EBP-14]
0167:00401C9AMOV BYTE PTR [ECX+EAX+0000F000],00
0167:00401CA2MOV EDX,[EBP-14]
;以上代碼為在映射代碼偏移F000處寫入指令CALL 0014:0000
0167:00401CA5ADD EDX,01
;指令 A91400C20000共6個字節
0167:00401CA8MOV [EBP-14],EDX ;
0167:00401CABMOV ESI,0040213B
;要復制的代碼的起始地址
0167:00401CB0MOV EDI,[EBP-08]
;要復制代碼的目標地址(映射區域中)
0167:00401CB3MOV ECX,00402688
;402688為要復制的代碼的末地址
0167:00401CB8SUB ECX,ESI
0167:00401CBAREPZMOVSB;將代碼全部復制到映射區域
0167:00401CBCSGDTFWORD PTR [EBP-1C];這句開始就很關鍵了
0167:00401CC0LEA EAX,[EBP-001C]
0167:00401CC6MOV EAX,[EAX+02];取GDT線性基址
0167:00401CC9XOR EBX,EBX
0167:00401CCBSLDTBX;取LDT在GDT中的偏移
0167:00401CCEAND BX,-08
0167:00401CD2ADD EAX,EBX
0167:00401CD4MOV ECX,[EAX+02]
0167:00401CD7SHL ECX,08
0167:00401CDAMOV CL,[EAX+07]
0167:00401CDDROR ECX,08 ;以上計算出LDT線性基址
0167:00401CE0MOV [EBP-0C],ECX ;保存
0167:00401CE3MOV EAX,[EBP-30]
0167:00401CE6MOV ECX,[EBP-0C]
0167:00401CE9MOV [EAX+00000370],ECX
0167:00401CEFMOV EDX,[EBP-30]
0167:00401CF2MOV EAX,[EDX+0000036C]
0167:00401CF8MOV ECX,[EBP-0C]
0167:00401CFBMOV [EAX+0000FE00],ECX
;將LDT線性基址保存至映射代碼中
0167:00401D01MOV AX,CS
;得到當前代碼段描述符號
0167:00401D04AND AX,FFF8
0167:00401D08MOV [EBP-10],AX
0167:00401D0CMOV EDX,[EBP-10]
0167:00401D0FAND EDX,0000FFFF
;EDX為代碼段描述符在LDT中的偏移量
0167:00401D15MOV EAX,[EBP-30]
0167:00401D18MOVECX,[EAX+00000370] ;ECX此時為LDT線性基址
0167:00401D1EMOV EAX,[EBP-30]
0167:00401D21MOV EAX,[EAX+00000370]
;EAX此時為LDT線性基址
0167:00401D27MOV ESI,[EDX+ECX]
0167:00401D2AMOV [EAX+08],ESI
0167:00401D2DMOV ECX,[EDX+ECX+04]
;以上將當前代碼段描述符復制到
0167:00401D31MOV [EAX+0C],ECX;LDT第1項
0167:00401D34MOV EDX,[EBP-30]
0167:00401D37MOV EAX,[EDX+00000370]
0167:00401D3DMOV CL,[EAX+0D]
0167:00401D40AND CL,9F
0167:00401D43MOV EDX,[EBP-30]
0167:00401D46MOV EAX,[EDX+00000370]
0167:00401D4CMOV [EAX+0D],CL
;以上修改LDT第1項的DPL為0,則當由調用門轉到該段代碼時即獲得RING0權限
0167:00401D4FMOV EAX,[EBP-0C]
0167:00401D52ADD EAX,10 ;獲得LDT中索引為2的調用門地址
0167:00401D55MOV EBX,0040213B
0167:00401D5AMOV [EAX],EBX
0167:00401D5CMOV [EAX+04],EBX
0167:00401D5FMOV WORD PTR [EAX+02],000C
0167:00401D65MOV WORD PTR [EAX+04],EC00;調用門修改完畢
0167:00401D6BMOV ECX,[EBP-08]
0167:00401D6EMOV EDX,[WINMM!timeGetTime]
0167:00401D74MOV [ECX+0000FEE0]
;EDX;保存timeGetTime入口地址
...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,SetTimer,
timeGetSystemTime,QueryPerformanceCounter入口地址
0167:00401DD2MOV ECX,[EBP-08]
0167:00401DD5MOV EAX,[WINMM!timeGetTime]
0167:00401DDAMOV EBX,[EAX]
0167:00401DDCMOV [ECX+0000FE40],EBX
0167:00401DE2MOV EBX,[EAX+04]
0167:00401DE5MOV [ECX+0000FE44],EBX
;保存timeGetTime函數前8個字節指令
...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,timeGetSystemTime , QueryPerformanceCounter前8個字節指令
0167:00401E6DMOV BYTE PTR [ECX+0000FE90],E9
0167:00401E74MOV EAX,00402165
0167:00401E79SUB EAX,0040213B
;EAX為截獲代碼在映射代碼中的偏移
0167:00401E7EADD EAX,ECX;計算出截獲代碼的線性入口地址
0167:00401E80SUB EAX,[WINMM!timeGetTime]
0167:00401E86SUB EAX,05 ;JMP指令總長5個字節
0167:00401E89MOV [ECX+0000FE91],EAX
;計算生成從timeGetTime跳到截獲代碼的JMP指令并保存
...省略部分依次計算并生成GetTickCount,GetMessageTime,timeSetEvent,timeGetSystemTime , QueryPerformanceCounter跳到截獲代碼的JMP指令并保存
0167:00401F58CLI;關閉中斷,謹防修改代碼時發生意外
0167:00401F59MOV EAX,004021F3 ;
0167:00401F5ESUB EAX,0040213B;計算子程序在映射代碼中的偏移
0167:00401F63ADD EAX,[EBP-08];EAX=8xxx 00B8
0167:00401F66PUSHEAX;傳入參數EAX為修改timeGetTime代碼的
;子程序入口地址
0167:00401F67MOV EAX,[EBP-08];調用8xxx 0000
0167:00401F6ACALLEAX ;返回時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 POPEDI ;取出當前指令地址
XORDI , DI
MOVESI , EDI
;將64K內存首地址賦給ESI
;此時ESI=EDI=832A 0000
ADDESI , 0040 2102
SUBESI , 0040 213B ;求出映射代碼首地址
PUSH ESI
0167:832A 004B CALL EDI;ESI為傳進的參數
;返回時已經將timeGetTime代碼還原
0167:832A 004D CALL 832A 0052;
0167:832A 0052 POPEDI
XORDI ,DI;故技重施
CALL [EDI + 0000FEED];調用原timeGetTime函數
SUBEAX,[EDI + 0000 FF30]
;減去第一次調用timeGetTime的結果
MULDWORD PTR [EDI+0000 FE30]
;乘以用戶所指定的倍數
MOVEBX ,00100000
DIVEBX
;除以常數100000
ADDEAX ,[EDI+ 0000FE20]
MOVEAX,004021F3
SUBEAX,0040213B
ADDEAX,EDI
;以上指令為修改timeGetTime函數返回值
PUSH EAX
;EAX為傳進的參數
CALL EDI
;返回時又將timeGetTime首指令換成JMP
...恢復各寄存器的值,EAX中為修改后的返回值
RET ;此時[ESP]=40BF2C,執行RET將返回到游戲中去
;
0167:832A 0000 CALL 832A 0005
0167:832A 0005 POPEDI
XORDI ,DI;老套了撒^_^
MOVESI ,[EDI+0000 FE00]
;此地址保存著LDT的線性基址
MOVEAX,[ESP+04]
MOV[ESI +10],AX
SHREAX,10
MOV[ESI+16],AX
;以上代碼將LDT中索引為2的調用門描述符的偏移改為傳入的參數
...
MOVEAX,0000 0F00
CALL EAX
;調用子程序修改timeGetTime代碼
0167:832A 0027 , ;RET4
;彈出參數,返回
;
0167:832A F000 CALL 0014:00000000
RET0
;
000C:832A 0097 CALL 832A 009C
000C:832A 009C POPEDI
MOVEAX,[EDI+0000 FE40]
MOVEBX,[EDI+0000 FEE0]
MOV[EBX],EAX
MOVEAX,[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 POPEDI
XORDI ,DI
...
MOVEAX,[EDI+0000 FE90]
MOVEBX,[EDI+0000 FEE0]
MOV[EBX],EAX
MOVEAX,[EDI+0000FE94]
MOV[EBX+04],EAX
RETF
注:EDI+0000 FE90 起前8個字節保存著JMP 832A 002A 指令
是由“齒輪”初始化部分代碼計算出來的,以上代碼將JMP 832A 002A
寫入timeGetTime函數
湖南省陽光電子技術學校常年開設:手機維修培訓、家電維修培訓、電工培訓、電腦維修培訓、焊工培訓--面向全國火爆招生!網址:http://www.hnygpx.com 報名電話:13807313137)。安置就業。考試合格頒發全國通用權威證書。采用我校多年來獨創的“模塊教學法”,理論與實踐相結合、原理+圖紙+機器三位一體的教學模式,半天理論,半天實踐,通俗易懂,確保無任何基礎者也能全面掌握維修技能、成為同行業中的佼佼者。工作(一期不會,免費學會為止)。
是全球第一款能改變游戲速度的程序。我起初用時覺得很神奇,久而久之就不禁思考其實現原理了,但苦于個人水平有限,始終不得其解,成了長駐于腦中揮散不去的大問號。
偶然一天在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反匯編,看看它的輸出函數:
__@@A">?ghWnd@@3PAUHWND__@@A
?gnHotKey1@@3KA
?gnHotKey2@@3KA
?gnHotKey3@@3KA
?gnHotKey4@@3KA
?nHook@@3HA
__@@@Z">?SetHook@@YAHPAUHWND__@@@Z
?UnHook@@YAHXZ
看函數名好象該dll只是安裝鉤子捕獲變速熱鍵的,與我的研究目的沒太大的關系, 跳過去!
再看看變速齒輪.exe的導入函數,timeGetTim、GetTickCount等時間相關的函數都
在里面。嘿,還有CreateFileMappingA和MapViewOfFileEx,看來“齒輪”是用這兩個函
數創建映射文件的。以下列出幾個關鍵的導入函數:
Hook.?gnHotKey1@@3KA
Hook.?gnHotKey2@@3KA
Hook.?gnHotKey3@@3KA
Hook.?gnHotKey4@@3KA
__@@@Z">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:00401B0EPUSH00
0167:00401B10PUSH00010000
0167:00401B15PUSH00
0167:00401B17PUSH04
0167:00401B19PUSH00
0167:00401B1BPUSHFF
0167:00401B1DCALL[KERNEL32!CreateFileMappingA]
;調用CreateFileMappingA
;調用形式如右:CreateFileMappingA(FF,0,4,0,10000,0)
0167:00401B23MOV ECX,[EBP-30]
0167:00401B26MOV [ECX+00000368],EAX
0167:00401B2CMOV DWORD PTR [EBP-14],80000000
0167:00401B33JMP 00401B41
0167:00401B35MOV EDX,[EBP-14]
0167:00401B38ADD EDX,00010000
;申請基址加0x10000
0167:00401B3EMOV [EBP-14],EDX
0167:00401B41MOV EAX,[EBP-14]
0167:00401B44PUSHEAX;映射文件基址
0167:00401B45PUSH00 ;映射的字節數
0167:00401B47PUSH00 ;文件偏移低32位
0167:00401B49PUSH00 ;文件偏移高32位
0167:00401B4BPUSH02 ;訪問模式
0167:00401B4DMOV ECX,[EBP-30]
0167:00401B50MOV EDX,[ECX+00000368]
0167:00401B56PUSHEDX
;CreateFileMappingA返回的映射文件句柄
0167:00401B57CALL[KERNEL32!MapViewOfFileEx]
; 調用形式如右:MapViewOfFileEx(EDX,2,0,0,0,EAX)
0167:00401B5DMOV ECX,[EBP-30]
;[EBP-30]為即將映射到2G之上
0167:00401B60MOV [ECX+0000036C],EAX
; 的代碼的數據域的起始地址
0167:00401B66MOV EDX,[EBP-30]
0167:00401B69CMP DWORD PTR [EDX+0000036C],00
;檢查MapViewOfFileEx
0167:00401B70JZ00401B74
;返回值,若為0則繼續調
0167:00401B72JMP 00401B76 ;調用MapViewOfFileEx
0167:00401B74JMP 00401B35 ;直至成功為止
0167:00401B76MOV EAX,[EBP-30]
0167:00401B79MOV ECX,[EAX+0000036C]
0167:00401B7FMOV [EBP-08],ECX
;映射文件起始地址存入[EBP-08]
0167:00401B82CALL[WINMM!timeGetTime]
0167:00401B88MOV [EBP-14],EAX
;將初次調用timeGetTime
0167:00401BA0MOV ECX,[EBP-08]
;的返回值保存到[EBP-14]
0167:00401BA3MOV EDX,[EBP-14]
;以及映射文件基址+FF30處
0167:00401BA6MOV [ECX+0000FF30],EDX
...省略的代碼類似的保存調用初次GetTickCount,QueryPerformanceCounter的返回值
0167:00401BEDMOV DWORD PTR [EBP-14],00000000
0167:00401BF4MOV EDX,[EBP-30]
0167:00401BF7MOV EAX,[EDX+0000036C]
0167:00401BFDMOV ECX,[EBP-14]
0167:00401C00MOV BYTE PTR [ECX+EAX+0000F000],9A
;9a為遠調用的指令碼
0167:00401C08MOV EDX,[EBP-14]
0167:00401C0BADD EDX,01
0167:00401C0EMOV [EBP-14],EDX
0167:00401C11MOV EAX,[EBP-14]
0167:00401C14ADD EAX,04
0167:00401C17MOV [EBP-14],EAX
0167:00401C1AMOV ECX,[EBP-30]
0167:00401C1DMOV EDX,[ECX+0000036C]
0167:00401C23MOV EAX,[EBP-14]
0167:00401C26MOV BYTE PTR [EAX+EDX+0000F000],14
;14為調用門描述符的索引
0167:00401C2EMOV ECX,[EBP-14]
0167:00401C31ADD ECX,01
0167:00401C34MOV [EBP-14],ECX
0167:00401C37MOV EDX,[EBP-30]
0167:00401C3AMOV EAX,[EDX+0000036C]
0167:00401C40MOV ECX,[EBP-14]
0167:00401C43MOV BYTE PTR [ECX+EAX+0000F000],00
;CALL指令其他部分
0167:00401C4BMOV EDX,[EBP-14]
0167:00401C4EADD EDX,01
0167:00401C51MOV [EBP-14],EDX
0167:00401C54MOV EAX,[EBP-30]
0167:00401C57MOV ECX,[EAX+0000036C]
0167:00401C5DMOV EDX,[EBP-14]
0167:00401C60MOV BYTE PTR [EDX+ECX+0000F000],C2
0167:00401C68MOV EAX,[EBP-14]
0167:00401C6BADD EAX,01
0167:00401C6EMOV [EBP-14],EAX
0167:00401C71MOV ECX,[EBP-30]
0167:00401C74MOV EDX,[ECX+0000036C]
0167:00401C7AMOV EAX,[EBP-14]
0167:00401C7DMOV BYTE PTR [EAX+EDX+0000F000],00
0167:00401C85MOV ECX,[EBP-14]
0167:00401C88ADD ECX,01
0167:00401C8BMOV [EBP-14],ECX
0167:00401C8EMOV EDX,[EBP-30]
0167:00401C91MOV EAX,[EDX+0000036C]
0167:00401C97MOV ECX,[EBP-14]
0167:00401C9AMOV BYTE PTR [ECX+EAX+0000F000],00
0167:00401CA2MOV EDX,[EBP-14]
;以上代碼為在映射代碼偏移F000處寫入指令CALL 0014:0000
0167:00401CA5ADD EDX,01
;指令 A91400C20000共6個字節
0167:00401CA8MOV [EBP-14],EDX ;
0167:00401CABMOV ESI,0040213B
;要復制的代碼的起始地址
0167:00401CB0MOV EDI,[EBP-08]
;要復制代碼的目標地址(映射區域中)
0167:00401CB3MOV ECX,00402688
;402688為要復制的代碼的末地址
0167:00401CB8SUB ECX,ESI
0167:00401CBAREPZMOVSB;將代碼全部復制到映射區域
0167:00401CBCSGDTFWORD PTR [EBP-1C];這句開始就很關鍵了
0167:00401CC0LEA EAX,[EBP-001C]
0167:00401CC6MOV EAX,[EAX+02];取GDT線性基址
0167:00401CC9XOR EBX,EBX
0167:00401CCBSLDTBX;取LDT在GDT中的偏移
0167:00401CCEAND BX,-08
0167:00401CD2ADD EAX,EBX
0167:00401CD4MOV ECX,[EAX+02]
0167:00401CD7SHL ECX,08
0167:00401CDAMOV CL,[EAX+07]
0167:00401CDDROR ECX,08 ;以上計算出LDT線性基址
0167:00401CE0MOV [EBP-0C],ECX ;保存
0167:00401CE3MOV EAX,[EBP-30]
0167:00401CE6MOV ECX,[EBP-0C]
0167:00401CE9MOV [EAX+00000370],ECX
0167:00401CEFMOV EDX,[EBP-30]
0167:00401CF2MOV EAX,[EDX+0000036C]
0167:00401CF8MOV ECX,[EBP-0C]
0167:00401CFBMOV [EAX+0000FE00],ECX
;將LDT線性基址保存至映射代碼中
0167:00401D01MOV AX,CS
;得到當前代碼段描述符號
0167:00401D04AND AX,FFF8
0167:00401D08MOV [EBP-10],AX
0167:00401D0CMOV EDX,[EBP-10]
0167:00401D0FAND EDX,0000FFFF
;EDX為代碼段描述符在LDT中的偏移量
0167:00401D15MOV EAX,[EBP-30]
0167:00401D18MOVECX,[EAX+00000370] ;ECX此時為LDT線性基址
0167:00401D1EMOV EAX,[EBP-30]
0167:00401D21MOV EAX,[EAX+00000370]
;EAX此時為LDT線性基址
0167:00401D27MOV ESI,[EDX+ECX]
0167:00401D2AMOV [EAX+08],ESI
0167:00401D2DMOV ECX,[EDX+ECX+04]
;以上將當前代碼段描述符復制到
0167:00401D31MOV [EAX+0C],ECX;LDT第1項
0167:00401D34MOV EDX,[EBP-30]
0167:00401D37MOV EAX,[EDX+00000370]
0167:00401D3DMOV CL,[EAX+0D]
0167:00401D40AND CL,9F
0167:00401D43MOV EDX,[EBP-30]
0167:00401D46MOV EAX,[EDX+00000370]
0167:00401D4CMOV [EAX+0D],CL
;以上修改LDT第1項的DPL為0,則當由調用門轉到該段代碼時即獲得RING0權限
0167:00401D4FMOV EAX,[EBP-0C]
0167:00401D52ADD EAX,10 ;獲得LDT中索引為2的調用門地址
0167:00401D55MOV EBX,0040213B
0167:00401D5AMOV [EAX],EBX
0167:00401D5CMOV [EAX+04],EBX
0167:00401D5FMOV WORD PTR [EAX+02],000C
0167:00401D65MOV WORD PTR [EAX+04],EC00;調用門修改完畢
0167:00401D6BMOV ECX,[EBP-08]
0167:00401D6EMOV EDX,[WINMM!timeGetTime]
0167:00401D74MOV [ECX+0000FEE0]
;EDX;保存timeGetTime入口地址
...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,SetTimer,
timeGetSystemTime,QueryPerformanceCounter入口地址
0167:00401DD2MOV ECX,[EBP-08]
0167:00401DD5MOV EAX,[WINMM!timeGetTime]
0167:00401DDAMOV EBX,[EAX]
0167:00401DDCMOV [ECX+0000FE40],EBX
0167:00401DE2MOV EBX,[EAX+04]
0167:00401DE5MOV [ECX+0000FE44],EBX
;保存timeGetTime函數前8個字節指令
...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,timeGetSystemTime , QueryPerformanceCounter前8個字節指令
0167:00401E6DMOV BYTE PTR [ECX+0000FE90],E9
0167:00401E74MOV EAX,00402165
0167:00401E79SUB EAX,0040213B
;EAX為截獲代碼在映射代碼中的偏移
0167:00401E7EADD EAX,ECX;計算出截獲代碼的線性入口地址
0167:00401E80SUB EAX,[WINMM!timeGetTime]
0167:00401E86SUB EAX,05 ;JMP指令總長5個字節
0167:00401E89MOV [ECX+0000FE91],EAX
;計算生成從timeGetTime跳到截獲代碼的JMP指令并保存
...省略部分依次計算并生成GetTickCount,GetMessageTime,timeSetEvent,timeGetSystemTime , QueryPerformanceCounter跳到截獲代碼的JMP指令并保存
0167:00401F58CLI;關閉中斷,謹防修改代碼時發生意外
0167:00401F59MOV EAX,004021F3 ;
0167:00401F5ESUB EAX,0040213B;計算子程序在映射代碼中的偏移
0167:00401F63ADD EAX,[EBP-08];EAX=8xxx 00B8
0167:00401F66PUSHEAX;傳入參數EAX為修改timeGetTime代碼的
;子程序入口地址
0167:00401F67MOV EAX,[EBP-08];調用8xxx 0000
0167:00401F6ACALLEAX ;返回時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 POPEDI ;取出當前指令地址
XORDI , DI
MOVESI , EDI
;將64K內存首地址賦給ESI
;此時ESI=EDI=832A 0000
ADDESI , 0040 2102
SUBESI , 0040 213B ;求出映射代碼首地址
PUSH ESI
0167:832A 004B CALL EDI;ESI為傳進的參數
;返回時已經將timeGetTime代碼還原
0167:832A 004D CALL 832A 0052;
0167:832A 0052 POPEDI
XORDI ,DI;故技重施
CALL [EDI + 0000FEED];調用原timeGetTime函數
SUBEAX,[EDI + 0000 FF30]
;減去第一次調用timeGetTime的結果
MULDWORD PTR [EDI+0000 FE30]
;乘以用戶所指定的倍數
MOVEBX ,00100000
DIVEBX
;除以常數100000
ADDEAX ,[EDI+ 0000FE20]
MOVEAX,004021F3
SUBEAX,0040213B
ADDEAX,EDI
;以上指令為修改timeGetTime函數返回值
PUSH EAX
;EAX為傳進的參數
CALL EDI
;返回時又將timeGetTime首指令換成JMP
...恢復各寄存器的值,EAX中為修改后的返回值
RET ;此時[ESP]=40BF2C,執行RET將返回到游戲中去
;
0167:832A 0000 CALL 832A 0005
0167:832A 0005 POPEDI
XORDI ,DI;老套了撒^_^
MOVESI ,[EDI+0000 FE00]
;此地址保存著LDT的線性基址
MOVEAX,[ESP+04]
MOV[ESI +10],AX
SHREAX,10
MOV[ESI+16],AX
;以上代碼將LDT中索引為2的調用門描述符的偏移改為傳入的參數
...
MOVEAX,0000 0F00
CALL EAX
;調用子程序修改timeGetTime代碼
0167:832A 0027 , ;RET4
;彈出參數,返回
;
0167:832A F000 CALL 0014:00000000
RET0
;
000C:832A 0097 CALL 832A 009C
000C:832A 009C POPEDI
MOVEAX,[EDI+0000 FE40]
MOVEBX,[EDI+0000 FEE0]
MOV[EBX],EAX
MOVEAX,[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 POPEDI
XORDI ,DI
...
MOVEAX,[EDI+0000 FE90]
MOVEBX,[EDI+0000 FEE0]
MOV[EBX],EAX
MOVEAX,[EDI+0000FE94]
MOV[EBX+04],EAX
RETF
注:EDI+0000 FE90 起前8個字節保存著JMP 832A 002A 指令
是由“齒輪”初始化部分代碼計算出來的,以上代碼將JMP 832A 002A
寫入timeGetTime函數
湖南省陽光電子技術學校常年開設:手機維修培訓、家電維修培訓、電工培訓、電腦維修培訓、焊工培訓--面向全國火爆招生!網址:http://www.hnygpx.com 報名電話:13807313137)。安置就業。考試合格頒發全國通用權威證書。采用我校多年來獨創的“模塊教學法”,理論與實踐相結合、原理+圖紙+機器三位一體的教學模式,半天理論,半天實踐,通俗易懂,確保無任何基礎者也能全面掌握維修技能、成為同行業中的佼佼者。工作(一期不會,免費學會為止)。