|
深入游戏变速底层原理以及内核变速的实现
前言
当你接触了一款叫做“变速齿轮”的软件,你可以玩上一整个星期。因为它实在是太酷炫了,无论网页(Flash、HTML5)、还是小软件,又或者是单机游戏,他总能给你惊喜,唯独网游不能!那么我们今天就来探究一下变速原理,并且我们试试看,能不能写出一个更牛逼的变速软件。
我们本章节内容大致如下图:
变速原理探究
变速齿轮(GearNT 0.46) 原理
我们拿个经典的变速软件开刀,这就是“变速齿轮”了,感觉变速的始祖就是他了。记得之前读过一篇文章,关于兄弟变速的,内容大致是说兄弟变速的创造历史,作者也是个很早很牛逼的程序辕。他写的变速器是受到了“变速齿轮”的启发,并且用了多个方面重新实现了一款变速软件,可以通过内核实现变速。所以,为了起到同样的这种启发作用,我们就拿“变速齿轮”这款软件做为学习的样本吧。
首先打开一个32位的记事本,作者的系统是 Win7x64 的,但是一代经典还是年事已高,只支持32位软件,所以吧用个32位的记事本研究下咯。然后在“变速齿轮”里对记事本调一下速度,随便调,我就不截图了。使用软件PCHunter,扫描记事本中的应用层钩子,我们可以看到如下图:
看到他被挂钩了8个API相关个 inline hook,其中两个API钩子能够实现“继承”,简单的说就是打开一个登录器使用了“变速齿轮”加速,然后登录器打开游戏时,游戏也会被加速。然后剩下的6个API都是关键,分别是
GetTickCountQueryPerformanceCounterGetMessageTimeSetTimertimeGetTimetimeSetEvent游戏常用的一般都是高精度计时,比如 Timer 啊什么的并不常用于游戏,因为精度一般。接下来就让我们深入了解“高精度计时API”
高精度计时API
GetTickCountGetTickCount64QueryPerformanceCountertimeGetTime顾名思义,就是精度很高,时间不会产生偏差,我们可以写个例子代码(使用 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[0x7FFE03B8] + __rdtsc()) >> (MEMORY[0x7FFE02ED] >> 2))大致原理就是(*a+rdtsc)>>(*b>>2),a、b是两个内存指针,我们尝试下
使用CE,打开程序的7FFE03B8地址,可以看到这个数字是在跳动(变化)的,我们要改的话不怎么好改,再打开7FFE02ED地址,可以看到一个固定的值不会变化。那我们尝试改一下这个地址里的值,然后发现改不了。然后又试了下写进程序里用WriteProcessMemory修改也改不动,再尝试使用VirtualProtectEx修改内存属性PAGE_EXECUTE_READWRITE,完了改写数值能成功,但是会导致GetTickCount和timeGetTime计时异常。哎呀,难道说…… 这个地址,有蹊跷!
深入 0x7FFE0000 SharedUserData
我们百度0x7FFE0000这个地址,得到了一个叫做 SharedUserData 的东东,是个内核的映射,Ring3下是只读的,所以如上,我们并改不了他的数据(确切的说改了,数据就不会同步,会导致大量的API无法工作)。
这个结构在应用层和内核的地址如下表
内核起始地址内核结束地址用户起始地址用户结束地址32 系统0xFFDF00000xFFDF0FFF0x7FFE00000x7FFE0FFF64 系统0xFFFFF780 000000000xFFFFF780 00000FFF0x7FFE00000x7FFE0FFF然后我们使用 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 : [260] 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 : [1] UChar +0x26a NativeProcessorArchitecture : Uint2B +0x26c NtMajorVersion : Uint4B +0x270 NtMinorVersion : Uint4B +0x274 ProcessorFeatures : [64] UChar +0x2b4 Reserved1 : Uint4B +0x2b8 Reserved3 : Uint4B +0x2bc TimeSlip : Uint4B +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE +0x2c4 AltArchitecturePad : [1] 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 : [2] UChar +0x2d8 ActiveConsoleId : Uint4B +0x2dc DismountCount : Uint4B +0x2e0 ComPlusPackage : Uint4B +0x2e4 LastSystemRITEventTickCount : Uint4B +0x2e8 NumberOfPhysicalPages : Uint4B +0x2ec SafeBootMode : UChar +0x2ed Reserved12 : [3] 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 : [1] Uint4B +0x2f8 TestRetInstruction : Uint8B +0x300 QpcFrequency : Int8B +0x308 SystemCallPad : [3] Uint8B +0x320 TickCount : _KSYSTEM_TIME +0x320 TickCountQuad : Uint8B +0x320 ReservedTickCountOverlay : [3] Uint4B +0x32c TickCountPad : [1] Uint4B +0x330 Cookie : Uint4B +0x334 CookiePad : [1] 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 : [14] UChar +0x380 UserModeGlobalLogger : [16] 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[0x7FFE03B8] + __rdtsc()) >> (MEMORY[0x7FFE02ED] >> 2)); +0x3b8 TscQpcBias : Uint8B +0x2ed Reserved12 : [3] UCharGetTickCountMEMORY[0x7FFE0004] * (MEMORY[0x7FFE0324] > 24) +0x004 TickCountMultiplier : Uint4B +0x320 TickCount : _KSYSTEM_TIMEGetTickCount64(MEMORY[0x7FFE0324] * (unsigned __int64)MEMORY[0x7FFE0004] > 24)timeGetTime@0x41B00000dword_41B28FE0 + (unsigned __int64)((MEMORY[0x7FFE0008] - 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 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|