當前位置:首頁>>軟件教程>>新聞內(nèi)容  
“變速齒輪”再研究
作者:幽幽黃桷蘭 發(fā)布時間:2004-2-20 9:34:08 | 【字體:

最新版本變速齒輪下載http://virtualinteriordefine.com/html/200312/20031212SOFT102101.html
變速齒輪使用說明和研究手記http://virtualinteriordefine.com/html/200312/20031212QBI102044.html

  提起“變速齒輪”(以下簡稱“齒輪”)這個軟件,大家應該都知道吧,該軟件號稱
是全球第一款能改變游戲速度的程序。我起初用時覺得很神奇,久而久之就不禁思考其實現(xiàn)原理了,但苦于個人水平有限,始終不得其解,成了長駐于腦中揮散不去的大問號。

  偶然一天在BBS上看到了一篇名為《“變速齒輪”研究手記》(以下簡稱《手記》)的文章,我如獲至寶,耐著性子把文章看完了,但之后還是有很多地方不解,不過還是有了比較模糊的認識:原來齒輪是通過截獲游戲程序對時間相關函數(shù)的調(diào)用并修改返回結果實現(xiàn)的呀。

  為了徹徹底底地弄清齒輪的原理,我這次打算豁出去了。考慮到《手記》的作者從是研究的“齒輪”的反匯編代碼的,那我也照樣從反匯編代碼開始。不過自認為匯編功底不夠,又從圖書館借了幾本關于Windows底層機制和386匯編的書,在經(jīng)過差不多兩周的“修行”之后,自我感覺有點好啦,哈哈,我也有點要迫不及待地把“齒輪”大卸八塊了!

  在動手之前,我又把《手記》看了一遍,這次可就清楚多了:通過調(diào)用門跳到Ring0級代碼段,修改各系統(tǒng)時間相關函數(shù)的前8個字節(jié)為jmp指令,轉跳到“齒輪”映射到2G之上的代碼,達到截獲對各系統(tǒng)時間相關函數(shù)的調(diào)用的目的。但同時我的疑惑也更明確了:
    1.“齒輪”怎樣建立指向自己映射到2G以上內(nèi)存的代碼的調(diào)用門描述符的;
    2.“齒輪”怎樣將自己的代碼映射到2G以上線性地址的;
    3.映射到2G之上的代碼是怎樣做到在代碼基址更改的情況仍能正確運行的

  帶著這樣的疑問,我正式開始了對“齒輪”反匯編代碼的分析。工具嘛,不用說當
然是Softice for Windows98、W32Dasm,OK,出發(fā)啦!

  我的“齒輪”版本是0.221 for win98和winme的,內(nèi)含有兩個文件(變速齒輪.exe
和Hook.dll)。先看看Hook.dll里面有些什么,用W32Dasm將Hook.dll反匯編,看看它的輸出函數(shù):

     ?ghWnd@@3PAUHWND__@@A
     ?gnHotKey1@@3KA
     ?gnHotKey2@@3KA
     ?gnHotKey3@@3KA
     ?gnHotKey4@@3KA
     ?nHook@@3HA
     ?SetHook@@YAHPAUHWND__@@@Z
     ?UnHook@@YAHXZ


  看函數(shù)名好象該dll只是安裝鉤子捕獲變速熱鍵的,與我的研究目的沒太大的關系, 跳過去!
  再看看變速齒輪.exe的導入函數(shù),timeGetTim、GetTickCount等時間相關的函數(shù)都
在里面。嘿,還有CreateFileMappingA和MapViewOfFileEx,看來“齒輪”是用這兩個函
數(shù)創(chuàng)建映射文件的。以下列出幾個關鍵的導入函數(shù):

     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函數(shù)的執(zhí)行情況。

  我先寫了個Win32 APP (以下簡稱APP),當左擊客戶區(qū)時會調(diào)用timeGetTime并將返回的結果輸出至客戶區(qū)。運行這個程序,打開“齒輪”,改變當前速度。

  Ctrl + D 呼出Softice,bpx timeGetTime ,退出,再左擊APP客戶區(qū),Softice跳出。哈,果然timeGetTime函數(shù)的首指令成了jmp 8xxx 002A ,好F8繼續(xù)執(zhí)行,進入了“ 齒輪”映射到2G線性地址之上的代碼。一路F8下去,發(fā)現(xiàn)接著“齒輪”把timeGetTime 首指令恢復,并再次調(diào)用timeGetTime,這樣就得到了timeGetTime的正確結果,保存結果!褒X輪”再把timeGetTime首指令又改為jmp 8xxx 002A 。接下來都猜得到“齒輪”要干什么了!沒錯,將得到的返回值修改后返回至調(diào)用timeGetTime的程序APP。

  我仔細分析了一下,“齒輪”修改返回值的公式如下: 
                    倍數(shù)*(返回值-第一次調(diào)用timeGetTime的返回值)
修改后的返回值=---------------------------------------------------+上一次修改后的返回值
                                                  100000 
  公式中“上次修改后的返回值”是自己猜測的未經(jīng)證實,僅供參考。

  代碼分析已經(jīng)進行一部分了,可我之前的疑問仍未解決,“齒輪”是怎么將代碼映
射的?又是怎么得到修改代碼的權限的?

  既然“齒輪”中調(diào)用了CreateFileMappingA,我想其安裝調(diào)用門,映射代碼的初始化部分應該就在調(diào)用該函數(shù)代碼的附近。好,沿著這個思路,呼出Softice,在CreateF ileMappingA處設置斷點,將“齒輪”關閉后再運行。Softice跳出,停在了CreateFile MappingA處,F(xiàn)11回到“齒輪”的代碼。看到了“齒輪”調(diào)用CreateFileMappingA的形式如下:

  CreateFileMappingA(FF,0,4,0,10000,0);
  可見“齒輪”創(chuàng)建了長度為0x10000的映射文件,繼續(xù),“齒輪”接著又調(diào)用MapViewOfFileEx,調(diào)用形式如下:
  MapViewOfFileEx(EDX,2,0,0,0,EAX);
  //EDX為CreateFileMappingA返回的映射文件句柄
  //EAX為申請映射代碼的基址,第一次調(diào)用時EAX為0x8000 0000
 

  這里就是關鍵了,“齒輪”要將映射文件映射至基址為0x8000 0000 的內(nèi)存空間中,可并不見得Windows就真的允許其映射呀?果然,“齒輪”在在調(diào)用之后判斷返回值是否有效,無效則將上次申請的基址加上0x1000,再次調(diào)用MapViewOfFileEx,一直循環(huán)到成功為止,再將返回的地址保存。

  接下來“齒輪”將原“齒輪”exe中的截獲API的代碼逐字節(jié)拷貝到映射區(qū)域去。至此,“齒輪”已經(jīng)將關鍵代碼映射到2G以上線性地址中了。

  我再F8,哈哈,和熟悉的SGDT指令打了個照面!褒X輪”保存全局描述符表線性基 址,再用SLDT指令保存局部描述符表索引,計算出LDT基址。接著呢“齒輪”在局部描述表中創(chuàng)建了一個特權等級為0的代碼段指向需要利用Ring0特權修改代碼的“齒輪”自己的代碼,并把局部描述表中索引為2的調(diào)用門指向的地址改為“齒輪”映射到高于2G的代碼。

  然后“齒輪”依次調(diào)用各時間相關的API,保存其返回值留做計算返回時結果用。
“齒輪”又依次調(diào)用映射到高于2G的代碼修改各API的首指令。到了這里,“齒輪”的初始化部分就結束了,只等著還蒙在鼓里的游戲上鉤啦,哈哈!

  結束代碼只不過是作些恢復工作罷了,僅僅是初始化代碼的逆過程,所以就不再贅述(其實是我自己懶得看了,^_^!). 
  至此,我對“齒輪”的加速原理已有大致的了解,深刻感受到“齒輪”代碼的精巧, 所以覺得有必要將"齒輪"中所運用到的一些技巧作一個總結:

1.基址無關代碼的編寫
  姑且以上面一句話作標題,^_^。看了“齒輪”的初始化代碼,知道其映射代碼的基址差不多是隨機的,那么“齒輪”是怎么保證映射后的代碼能正常運行的呢?如果 代碼是完全順序執(zhí)行的倒沒什么問題,但如果要調(diào)用自己映射代碼中的子程序呢?呵呵,就只有運行時計算出子程序的入口地址并調(diào)用了,不過還是要先得到映射代碼所在的地址才行!褒X輪”簡單地用兩條指令就得到當前正在執(zhí)行的指令的地址,具體如下(地址為假設的):

  0:0   call 5
  0:5   pop esi


  現(xiàn)在esi中的值就是5了,哈哈!

  這里的call用的是近調(diào)用,整條指令為E800000000,即為調(diào)用下一條指令.所進行的操作只不過是把下一條指令的地址入棧而已.再pop將返回地址(即pop指令本身的地址)取出.

2.修改調(diào)用門,生成jmp指令,修改代碼
  這些都是高度依賴于CPU的操作,技巧性也很強,主要是鉆了操作系統(tǒng)的漏洞。比如“齒輪”就是用SGDT,SLDT獲得全局和局部描述符表基址來安裝調(diào)用門,通過訪問調(diào)用門來獲取RING0權限作一些平時不為系統(tǒng)所允許的操作;而CIH病毒是用SIDT獲得中斷描述符表基址安裝中斷門然后出發(fā)軟中斷獲取RING0權限的,原理都是一樣的。這些在水木上討論過很多遍,大家都很熟悉,所以也就不敢班門弄斧,寫到此為止。

3.64K代碼編寫
  由調(diào)用CreateFileMappingA函數(shù)參數(shù)可知“齒輪”只映射10000(64K)大小的區(qū)域,所以其映射在2G之上的代碼和數(shù)據(jù)決不能大于64K。我想作者之所以選擇64K為映射區(qū)域的大小,可能是與調(diào)用子程序或數(shù)據(jù)時容易計算地址有關。在映射代碼的任意一處得到當前指令地址之后將其低16位置0即可得到映射代碼的基地址,再加上子程序入口或數(shù)據(jù)的偏移即可求得其絕對地址。
 
我的評論:

  一句話:佩服“齒輪”的作者王榮先生。

  “齒輪”的代碼表現(xiàn)他對windows運行機制的深刻理解以及深厚的匯編功底還有豐富的想象力。對我來說“齒輪”仿佛就是一件精美的藝術品,每個細處都很值得玩味一 番,所以我才在看過“齒輪”代碼之后有了把我的分析過程用筆寫下來的沖動。但同時 我又不得不承認“齒輪”的功能的實現(xiàn)是依靠其高度技巧化的代碼實現(xiàn)的,換句話說就 是這種的方法局限性實在是太大了。不就是截獲API嘛,用的著這么麻煩嗎?

  為了證實自己的想法,我在Codeguru上直接找了個HOOK API 的代碼,該代碼是通過安裝WH_CBT類型全局鉤子在所有被插入DLL的進程中修改進程PE映像的輸入節(jié)達到截獲API的(這種方法在《windows核心編程》中有詳細說明)。把代碼稍做修改,就能工作了(在星際爭霸下試過,可以改變游戲速度)。盡管只在98下試過,但我覺得肯定也能在2000下用,因為代碼中只用了一兩句匯編指令,而且整個程序都是在RING3下運行的,沒有作出什么出軌的舉動。當然這種方法也有缺點,就是對用Loadlibrary加載WINMM.dll再用GetProcAddress獲取timeGetTime地址的API調(diào)用不起作用(原因在《windows核心編程》中有說明)。

  我打算在將測試用程序稍稍完善后再公布源代碼,屆時歡迎大家下載。
 
我的感謝:
  在我徹底弄清“齒輪”的代碼之后,已經(jīng)是第三天的上午了,無奈自己才疏學淺,全不像《手記》的作者只花了一個晚上就弄清楚,我可是花了一個上午、兩個下午、兩個晚上才結束了戰(zhàn)斗,實在是慚愧呀。

  自己之所以能自得其樂地堅持了兩天多,是與寢室兄弟小強的支持分不開的。窮 困潦倒的我在這幾天不知道總共抽了他多少支煙,無以為報,只有在這里說一聲謝謝了!另外還要感謝sunlie非常地閱讀本文,指出了原文中的錯誤并提出了非常寶貴的意見!

  最后要說的就是個人水平有限,文中難免出現(xiàn)錯誤,歡迎大家討論!^_^

附A:
  使用工具:Softice for Windows98,W32Dasm,VisualC++ 6.0
  操作系統(tǒng):Window98 2nd
  分析目標:變速齒輪 for 98me 版本:0.221
  參考書籍或文章:
    80x86匯編語言程序設計教程     楊季文等編著   清華大學出版社
    windows剖析--初始化篇及內(nèi)核篇                清華大學出版社
    虛擬設備驅動程序開發(fā)
    intel 32位系統(tǒng)軟件編程
    80x86指令參考手冊
    《“變速齒輪”研究手記》

附B:
   “齒輪”關鍵代碼完全注釋
   一、初始化部分(從"齒輪"調(diào)用CreateFileMappingA函數(shù)開始分析)
                  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]
   ;調(diào)用CreateFileMappingA
   ;調(diào)用形式如右: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       ;映射的字節(jié)數(shù)
                  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]
  ; 調(diào)用形式如右:MapViewOfFileEx(EDX,2,0,0,0,EAX)
                  0167:00401B5D  MOV       ECX,[EBP-30]
  ;[EBP-30]為即將映射到2G之上
                  0167:00401B60  MOV       [ECX+0000036C],EAX
  ; 的代碼的數(shù)據(jù)域的起始地址
                  0167:00401B66  MOV       EDX,[EBP-30]
                  0167:00401B69  CMP       DWORD PTR [EDX+0000036C],00
  ;檢查MapViewOfFileEx
                  0167:00401B70  JZ          00401B74
                ;返回值,若為0則繼續(xù)調(diào)
                  0167:00401B72  JMP       00401B76   ;調(diào)用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
  ;將初次調(diào)用timeGetTime
                 0167:00401BA0  MOV       ECX,[EBP-08]
  ;的返回值保存到[EBP-14]
                 0167:00401BA3  MOV       EDX,[EBP-14]
  ;以及映射文件基址+FF30處
                 0167:00401BA6  MOV       [ECX+0000FF30],EDX
 ...省略的代碼類似的保存調(diào)用初次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為遠調(diào)用的指令碼
                 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為調(diào)用門描述符的索引
                 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個字節(jié)
                 0167:00401CA8  MOV       [EBP-14],EDX ;
                 0167:00401CAB  MOV       ESI,0040213B
  ;要復制的代碼的起始地址
                 0167:00401CB0  MOV       EDI,[EBP-08]
  ;要復制代碼的目標地址(映射區(qū)域中)
                 0167:00401CB3  MOV       ECX,00402688
  ;402688為要復制的代碼的末地址
                 0167:00401CB8  SUB       ECX,ESI
                 0167:00401CBA  REPZ      MOVSB  ;將代碼全部復制到映射區(qū)域
                 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,則當由調(diào)用門轉到該段代碼時即獲得RING0權限 
                 0167:00401D4F  MOV       EAX,[EBP-0C] 
                 0167:00401D52  ADD       EAX,10       ;獲得LDT中索引為2的調(diào)用門地址 
                 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  ;調(diào)用門修改完畢 
                 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函數(shù)前8個字節(jié)指令
     ...省略部分依次保存GetTickCount,GetMessageTime,timeSetEvent,timeGetSystemTime , QueryPerformanceCounter前8個字節(jié)指令 
                 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個字節(jié) 
                 0167:00401E89  MOV       [ECX+0000FE91],EAX
            ;計算生成從timeGetTime跳到截獲代碼的JMP指令并保存
 
       ...省略部分依次計算并生成GetTickCount,GetMessageTime,timeSetEvent,        timeGetSystemTime , QueryPerformanceCounter跳到截獲代碼的JMP指令并保存 
  
                 0167:00401F58  CLI    ;關閉中斷,謹防修改代碼時發(fā)生意外 
                 0167:00401F59  MOV       EAX,004021F3         ; 
                 0167:00401F5E  SUB       EAX,0040213B;計算子程序在映射代碼中的偏移 
                 0167:00401F63  ADD       EAX,[EBP-08]          ;EAX=8xxx 00B8 
                 0167:00401F66  PUSH      EAX    ;傳入?yún)?shù)EAX為修改timeGetTime代碼的
                                                           ;子程序入口地址 
                 0167:00401F67  MOV       EAX,[EBP-08]          ;調(diào)用8xxx 0000 
                 0167:00401F6A  CALL      EAX       ;返回時timeGetTime首指令被更改
 
          ...省略部分依次修改GetTickCount,GetMessageTime,timeSetEvent,
            timeGetSystemTime , QueryPerformanceCounter函數(shù)的首指令 
  
                 0167:00401FF   SETI      ;設置中斷,初始化代碼結束
  二、截獲時間函數(shù)部分(以timeGetTime為例子,代碼以跟蹤順序列出)
           timeGetTime
                        JMP 832A 002A
          ;這是timeGetTime被修改后的首指令 
                 0167:832A 002A         CLI
          ;此時[esp]=40BF2C,即游戲程序中調(diào)用timeGetTime函數(shù)的下一條指令
          ...(6個)各寄存器分別入棧 且MOV EBP,ESP 
                 0167:832A 0033         CALL   832A 0038
          ;將當前EIP入棧(即下一條指令的地址) 
                 0167:832A 0038         POP    EDI       ;取出當前指令地址 
                                        XOR    DI   , DI
                                        MOV    ESI , EDI
         ;將64K內(nèi)存首地址賦給ESI
         ;此時ESI=EDI=832A 0000
                                        ADD    ESI , 0040 2102 
                                        SUB    ESI , 0040 213B ;求出映射代碼首地址 
                                        PUSH   ESI 
                 0167:832A 004B         CALL   EDI        ;ESI為傳進的參數(shù)
                                           ;返回時已經(jīng)將timeGetTime代碼還原 
                 0167:832A 004D         CALL   832A 0052    ; 
                 0167:832A 0052         POP    EDI
                                        XOR    DI ,DI        ;故技重施 
                                        CALL   [EDI + 0000FEED];調(diào)用原timeGetTime函數(shù)
                                        SUB    EAX,[EDI + 0000 FF30]
        ;減去第一次調(diào)用timeGetTime的結果
                                        MUL    DWORD PTR [EDI+0000 FE30]
        ;乘以用戶所指定的倍數(shù)
                                        MOV    EBX ,00100000
                                        DIV    EBX
        ;除以常數(shù)100000
                                        ADD    EAX ,[EDI+ 0000FE20] 
                                        MOV    EAX,004021F3 
                                        SUB    EAX,0040213B 
                                        ADD    EAX,EDI
        ;以上指令為修改timeGetTime函數(shù)返回值 
                                        PUSH   EAX
        ;EAX為傳進的參數(shù) 
                                        CALL   EDI
        ;返回時又將timeGetTime首指令換成JMP
        ...恢復各寄存器的值,EAX中為修改后的返回值 
                                        RET ;此時[ESP]=40BF2C,執(zhí)行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的調(diào)用門描述符的偏移改為傳入的參數(shù)
         ... 
                                        MOV    EAX,0000 0F00 
                                        CALL   EAX
        ;調(diào)用子程序修改timeGetTime代碼
         0167:832A 0027                 RET    4
        ;彈出參數(shù),返回
        ; 
                 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個字節(jié)為原timeGetTime函數(shù)的指令
            EDI+0000 FEE0保存著timeGetTime函數(shù)的入口地址
            以上即恢復timeGetTime前8個字節(jié)的代碼
        ; 
                 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個字節(jié)保存著JMP 832A 002A 指令
            是由“齒輪”初始化部分代碼計算出來的,以上代碼將JMP 832A 002A
            寫入timeGetTime函數(shù)
--
 
--
 
                      Be...Be...BeCOMe...


文章來源:BBBKOM.CQUPT
·變速齒輪使用說明和研究手記
 放生
 愚愛
 夠愛
 觸電
 白狐
 葬愛
 光榮
 畫心
 火花
 稻香
 小酒窩
 下雨天
 右手邊
 安靜了
 魔杰座
 你不像她
 邊做邊愛
 擦肩而過
 我的答鈴
 懷念過去
 等一分鐘
 放手去愛
 冰河時代
 你的承諾
 自由飛翔
 原諒我一次
 吻的太逼真
 左眼皮跳跳
 做你的愛人
 一定要愛你
 飛向別人的床
 愛上別人的人
 感動天感動地
 心在跳情在燒
 玫瑰花的葬禮
 有沒有人告訴你
 即使知道要見面
 愛上你是一個錯
 最后一次的溫柔
 愛上你是我的錯
 怎么會狠心傷害我
 不是因為寂寞才想
 親愛的那不是愛情
 難道愛一個人有錯
 寂寞的時候說愛我