“变速齿轮”研究手记

谁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:

 

 

简介

SoftICECompuware 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

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值