JuncoJet 发表于 2019-5-9 10:57

深入游戏变速底层原理以及内核变速的实现

本帖最后由 JuncoJet 于 2019-5-9 16:22 编辑

# 深入游戏变速底层原理以及内核变速的实现
## 前言
当你接触了一款叫做“变速齿轮”的软件,你可以玩上一整个星期。因为它实在是太酷炫了,无论网页(Flash、HTML5)、还是小软件,又或者是单机游戏,他总能给你惊喜,唯独网游不能!那么我们今天就来探究一下变速原理,并且我们试试看,能不能写出一个更牛逼的变速软件。

我们本章节内容大致如下图:



## 变速原理探究
### 变速齿轮(GearNT 0.46) 原理
我们拿个经典的变速软件开刀,这就是“变速齿轮”了,感觉变速的始祖就是他了。记得之前读过一篇文章,关于兄弟变速的,内容大致是说兄弟变速的创造历史,作者也是个很早很牛逼的程序辕。他写的变速器是受到了“变速齿轮”的启发,并且用了多个方面重新实现了一款变速软件,可以通过内核实现变速。所以,为了起到同样的这种启发作用,我们就拿“变速齿轮”这款软件做为学习的样本吧。
:https://down.52pojie.cn/Tools/Anti_Rootkit/PCHunter_free.zip
首先打开一个32位的记事本,作者的系统是 Win7x64 的,但是一代经典还是年事已高,只支持32位软件,所以吧用个32位的记事本研究下咯。然后在“变速齿轮”里对记事本调一下速度,随便调,我就不截图了。使用软件,扫描记事本中的应用层钩子,我们可以看到如下图:



看到他被挂钩了8个API相关个 inline hook,其中两个API钩子能够实现“继承”,简单的说就是打开一个登录器使用了“变速齿轮”加速,然后登录器打开游戏时,游戏也会被加速。然后剩下的6个API都是关键,分别是

```
GetTickCount
QueryPerformanceCounter
GetMessageTime
SetTimer
timeGetTime
timeSetEvent
```

游戏常用的一般都是高精度计时,比如 Timer 啊什么的并不常用于游戏,因为精度一般。接下来就让我们深入了解“高精度计时API”

### 高精度计时API

```
GetTickCount
GetTickCount64
QueryPerformanceCounter
timeGetTime
```
顾名思义,就是精度很高,时间不会产生偏差,我们可以写个例子代码(使用 MinGW 编译)如下




代码由`for(;;){}`来产生无限循环,`Sleep(1000)`来产生1秒的延迟。然后分别由3个高精度计时API来计算时间差,并吧计算结果显示出来。由图上可以看出,`QueryPerformanceCounter`和`timeGetTime`的计算精度还是可以的,`GetTickCount`的精度略差。我们暂且不管精度怎么样,接来下操起家伙(IDA)就是干。

## 逆向与深入计时API
### QueryPerformanceCounter 原理详解
我们就先对`QueryPerformanceCounter`进行逆向吧,毕竟所有高精度计时API中,精度最最高的就是他。研究他比较有“说服力”(假的,纯粹是因为很想知道这个API的工作原理以及为毛精度如此高。我们先试用 OllyICE 跟一下发现`QueryPerformanceCounter`的原型是`ntdll.RtlQueryPerformanceCounter`,我们就使用IDA对ntdll进行逆向。




由于算法还是比较复杂的,而且运算比较多,用的汇编指令不是特别常见,所以我们还是直接F5吧



我们得到他的返回值的算法应该就是这样

```
(LARGE_INTEGER)((MEMORY + __rdtsc()) >> (MEMORY >> 2))
```

大致原理就是`(*a+rdtsc)>>(*b>>2)`,a、b是两个内存指针,我们尝试下

使用CE,打开程序的7FFE03B8地址,可以看到这个数字是在跳动(变化)的,我们要改的话不怎么好改,再打开7FFE02ED地址,可以看到一个固定的值不会变化。那我们尝试改一下这个地址里的值,然后发现改不了。然后又试了下写进程序里用`WriteProcessMemory`修改也改不动,再尝试使用`VirtualProtectEx`修改内存属性`PAGE_EXECUTE_READWRITE`,完了改写数值能成功,但是会导致`GetTickCount`和`timeGetTime`计时异常。哎呀,难道说…… 这个地址,有蹊跷!





### 深入 0x7FFE0000 SharedUserData
我们百度0x7FFE0000这个地址,得到了一个叫做 SharedUserData 的东东,是个内核的映射,Ring3下是只读的,所以如上,我们并改不了他的数据(确切的说改了,数据就不会同步,会导致大量的API无法工作)。

这个结构在应用层和内核的地址如下表

| |内核起始地址|内核结束地址|用户起始地址|用户结束地址|
|---|---------|------------|------------|------------|
|32 系统|0xFFDF0000|0xFFDF0FFF|0x7FFE0000|0x7FFE0FFF|
|64 系统|0xFFFFF780 00000000|0xFFFFF780 00000FFF|0x7FFE0000|0x7FFE0FFF|

然后我们使用 LiveKD / WinDbg 对 KUSER_SHARED_DATA 的结构进行列出,在我的电脑(Win7x64 7601)上,他的结构如下

```
WDFLDR!KUSER_SHARED_DATA
   +0x000 TickCountLowDeprecated : Uint4B
   +0x004 TickCountMultiplier : Uint4B
   +0x008 InterruptTime    : _KSYSTEM_TIME
   +0x014 SystemTime       : _KSYSTEM_TIME
   +0x020 TimeZoneBias   : _KSYSTEM_TIME
   +0x02c ImageNumberLow   : Uint2B
   +0x02e ImageNumberHigh: Uint2B
   +0x030 NtSystemRoot   : Wchar
   +0x238 MaxStackTraceDepth : Uint4B
   +0x23c CryptoExponent   : Uint4B
   +0x240 TimeZoneId       : Uint4B
   +0x244 LargePageMinimum : Uint4B
   +0x248 AitSamplingValue : Uint4B
   +0x24c AppCompatFlag    : Uint4B
   +0x250 RNGSeedVersion   : Uint8B
   +0x258 GlobalValidationRunlevel : Uint4B
   +0x25c TimeZoneBiasStamp : Int4B
   +0x260 Reserved2      : Uint4B
   +0x264 NtProductType    : _NT_PRODUCT_TYPE
   +0x268 ProductTypeIsValid : UChar
   +0x269 Reserved0      : UChar
   +0x26a NativeProcessorArchitecture : Uint2B
   +0x26c NtMajorVersion   : Uint4B
   +0x270 NtMinorVersion   : Uint4B
   +0x274 ProcessorFeatures : UChar
   +0x2b4 Reserved1      : Uint4B
   +0x2b8 Reserved3      : Uint4B
   +0x2bc TimeSlip         : Uint4B
   +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
   +0x2c4 AltArchitecturePad : Uint4B
   +0x2c8 SystemExpirationDate : _LARGE_INTEGER
   +0x2d0 SuiteMask      : Uint4B
   +0x2d4 KdDebuggerEnabled : UChar
   +0x2d5 MitigationPolicies : UChar
   +0x2d5 NXSupportPolicy: Pos 0, 2 Bits
   +0x2d5 SEHValidationPolicy : Pos 2, 2 Bits
   +0x2d5 CurDirDevicesSkippedForDlls : Pos 4, 2 Bits
   +0x2d5 Reserved         : Pos 6, 2 Bits
   +0x2d6 Reserved6      : UChar
   +0x2d8 ActiveConsoleId: Uint4B
   +0x2dc DismountCount    : Uint4B
   +0x2e0 ComPlusPackage   : Uint4B
   +0x2e4 LastSystemRITEventTickCount : Uint4B
   +0x2e8 NumberOfPhysicalPages : Uint4B
   +0x2ec SafeBootMode   : UChar
   +0x2ed Reserved12       : UChar
   +0x2f0 SharedDataFlags: Uint4B
   +0x2f0 DbgErrorPortPresent : Pos 0, 1 Bit
   +0x2f0 DbgElevationEnabled : Pos 1, 1 Bit
   +0x2f0 DbgVirtEnabled   : Pos 2, 1 Bit
   +0x2f0 DbgInstallerDetectEnabled : Pos 3, 1 Bit
   +0x2f0 DbgLkgEnabled    : Pos 4, 1 Bit
   +0x2f0 DbgDynProcessorEnabled : Pos 5, 1 Bit
   +0x2f0 DbgConsoleBrokerEnabled : Pos 6, 1 Bit
   +0x2f0 DbgSecureBootEnabled : Pos 7, 1 Bit
   +0x2f0 SpareBits      : Pos 8, 24 Bits
   +0x2f4 DataFlagsPad   : Uint4B
   +0x2f8 TestRetInstruction : Uint8B
   +0x300 QpcFrequency   : Int8B
   +0x308 SystemCallPad    : Uint8B
   +0x320 TickCount      : _KSYSTEM_TIME
   +0x320 TickCountQuad    : Uint8B
   +0x320 ReservedTickCountOverlay : Uint4B
   +0x32c TickCountPad   : Uint4B
   +0x330 Cookie         : Uint4B
   +0x334 CookiePad      : Uint4B
   +0x338 ConsoleSessionForegroundProcessId : Int8B
   +0x340 TimeUpdateSequence : Uint8B
   +0x348 BaselineSystemTimeQpc : Uint8B
   +0x350 BaselineInterruptTimeQpc : Uint8B
   +0x358 QpcSystemTimeIncrement : Uint8B
   +0x360 QpcInterruptTimeIncrement : Uint8B
   +0x368 QpcSystemTimeIncrement32 : Uint4B
   +0x36c QpcInterruptTimeIncrement32 : Uint4B
   +0x370 QpcSystemTimeIncrementShift : UChar
   +0x371 QpcInterruptTimeIncrementShift : UChar
   +0x372 Reserved8      : UChar
   +0x380 UserModeGlobalLogger : Uint2B
   +0x3a0 ImageFileExecutionOptions : Uint4B
   +0x3a4 LangGenerationCount : Uint4B
   +0x3a8 Reserved4      : Uint8B
   +0x3b0 InterruptTimeBias : Uint8B
   +0x3b8 TscQpcBias       : Uint8B
   +0x3c0 ActiveProcessorCount : Uint4B
   +0x3c4 ActiveGroupCount : UChar
   +0x3c5 Reserved9      : UChar
   +0x3c6 TscQpcData       : Uint2B
   +0x3c6 TscQpcEnabled    : UChar
   +0x3c7 TscQpcShift      : UChar
   +0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER
   +0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER
   +0x3d8 XState         : _XSTATE_CONFIGURATION
```

然后顺个便,把其他几个API也给逆了,对照上表,我们得到详细的注解。

```
QueryPerformanceCounter
(LARGE_INTEGER)((MEMORY + __rdtsc()) >> (MEMORY >> 2));
   +0x3b8 TscQpcBias       : Uint8B
   +0x2ed Reserved12       : UChar
GetTickCount
MEMORY * (MEMORY << 8)
   + (MEMORY * (unsigned __int64)MEMORY >> 24)
   +0x004 TickCountMultiplier : Uint4B
   +0x320 TickCount      : _KSYSTEM_TIME
GetTickCount64
(MEMORY * (unsigned __int64)MEMORY << 8)
   + (MEMORY * (unsigned __int64)MEMORY >> 24)
timeGetTime@0x41B00000
dword_41B28FE0 + (unsigned __int64)((MEMORY - qword_41B28FD8) / 10000)
   +0x008 InterruptTime    : _KSYSTEM_TIME

```

这边还有个 _KSYSTEM_TIME 结构,这个结构是3个DWORD32。

```
ntdll!_KSYSTEM_TIME
   +0x000 LowPart          : Uint4B
   +0x004 High1Time      : Int4B
   +0x008 High2Time      : Int4B
```

有了他,我们能够事倍功半,呸!事半功倍。接下来我们就要进内核玩Ring0了,想想就有些小激动。

## 深入与验证
### 使用CE、MinGW(G++)验证
我们先来用 MinGW 写一个小程序,程序原理如下图


程序根据这个原理,来推出新值下的时间,和老的时间比值。这个值也就是之后能否实现加速的关键,如果无法反推出这个值,那么就无法实现加速。我们开始写代码吧





程序完美运行!鼓掌,那我们就确定了这个值,能被用于加速,里面的数学原理我也不懂,但从结果表明,应该和4(1<<2)的倍数有关。得到了这个值,我们接下来就用CE的内核模式,来修改内核数据。第一步先开启CE的内核设置,按照我下图这么设置



然后打开 System 进程,找到地址0xFFFFF780000002ED(作者电脑是64位,32位请用0xFFDF02ED),尝试着 Ring0 改写(给作者 666)。我们先运行时钟小程序,然后把上面计算出来的二倍速数值填写进去。观察变化






可以看出,修改后了,成功加速了`QueryPerformanceCounter`,同时又不影响其他的计时API,那我们就验证完毕,接下来就是写自己的内核变速器了

### 变速器的实现(Win7x64 内核变速)
由于64位系统需要签名,作者又是穷diao,那没法自己写驱动了(其实以前写都是用kmdkit来写的,汇编写64位驱动没底气),那就直接考虑用WinRing0或者WinIo64来实现。
首先作者考虑的是WinRing0,因为有签名嘛,所以首选他,结果用的时候发现没有物理内存读写接口,然后就想着怎么开启,重新编译了一番,发现众多的坑。等到我把所有的DLL条件编译去除,然后重新编译后,用上发现,并不能用。一开始以为内核是有物理内存读写的,只是应用层的dll给去除了。结果仔细看内核的源码,真是十万个CNM。



从上面这幅图上完全看不出有问题是吧,感觉这个内核功能齐全,但是看下面这个,哎!



强烈的怀疑作者是不是弄了两份驱动,一份是收费PRO版本,支持物理内存读写,一份是免费版本,给大家拿来玩的。与其这样要装庞大的WDK重新编译驱动,不如直接用WinIo64这么想。那我们下面就用VB+WinIo64展开。

首先是为了能够运行没有签名的WinIo64.sys,我们不得不把系统的测试模式开出来,那下面一个命令搞定

```bcdedit /set testsigning on```

然后我们就编写代码,使用VB写简单又好用,代码如下图,很少的代码。


运行下,我们试试效果,咩哈哈哈!











使用检测,不产生任何的Ring3/Ring0钩子,堪称完美,还不赶紧试试
:https://down.52pojie.cn/Tools/Anti_Rootkit/PCHunter_free.zip

JuncoJet 发表于 2019-5-9 15:18

幻象 发表于 2019-5-9 15:15
请问楼主,一些APP买东西需要定点秒杀,不到时间不能购买,我能不能用这种变速软件稍微提前一点时间来实现 ...

同步对方服务器的时间,能抢的比较准,其他就没办法了

225298658 发表于 2019-5-15 15:29

免测试模式,驱动已经签名,覆盖原驱动文件即可:

hotbone 发表于 2019-5-9 11:25

大概1999年知道的这个软件吧,当初作者好像是为了一款荷兰游戏做的这个东西,拿名次赚奖金。
后来按键精灵前身,兄弟工作室的老大去要源码未果,自己用w32dasm反汇编后闭关几天研究出来了。{:1_921:}

JuncoJet 发表于 2019-5-9 11:50

涛之雨 发表于 2019-5-9 11:44
目测此贴要加精。。前排留名又如何?
不过有些游戏是没办法使用变速齿轮的。。。不知道为什么啊。。。使用 ...

原因是没有用系统的API,只用了intel指令

JuncoJet 发表于 2022-2-14 09:09

zdloveli 发表于 2022-2-13 23:24
楼主居然有在玩街头篮球吗? 哈哈,这个会不会封号,现在几乎看不到加速,不过最近有个西瓜PG就用着 ...

一局不会封,两局会,检测机制大约在10分钟左右

lijunyao 发表于 2019-5-9 11:51

不过,WIN7与WIN10是否通用呢?

JuncoJet 发表于 2019-5-9 12:30

CarroAro 发表于 2019-5-9 12:12
win7 64直接打開顯示winio無法完全加載怎麼回事呢?

因为驱动没签名,开测试模式用

CarroAro 发表于 2019-5-9 12:12

JuncoJet 发表于 2019-5-9 11:50
原因是没有用系统的API,只用了intel指令

win7 64直接打開顯示winio無法完全加載怎麼回事呢?

kantal 发表于 2020-2-27 15:15

厉害,这个值得学习

新人类 发表于 2019-5-9 11:22

膜拜一下,虽然看不懂,但觉得好厉害的样子

唯殇啊 发表于 2019-5-9 11:26

小白膜拜大佬,出成品吗

涛之雨 发表于 2019-5-9 11:44

目测此贴要加精。。前排留名又如何?
不过有些游戏是没办法使用变速齿轮的。。。不知道为什么啊。。。使用的时候会显示无法加载什么钩子。具体的记不清了 。。
是驱动保护吗?不像啊,是一个很小的游戏。。。

blmk 发表于 2019-5-9 11:49

好的前排围观
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 深入游戏变速底层原理以及内核变速的实现