首页 > 其他分享 >[QLIE] 封包接口Hook

[QLIE] 封包接口Hook

时间:2024-03-26 16:22:25浏览次数:14  
标签:封包 游戏 Hook LPTPack 加密 QLIE 读取

[QLIE] 封包接口Hook

这个主题快拖了半个月,中间一直没空写,今天看着实在有点久了,必须写一写了,不然就快忘记了。

起因

前不久 Happy Live Show Up 发了官中,但是又搞的奇奇怪怪的加密,很是无聊,稍微调了下和之前 ハミダシクリエイティブ 官中是挺像的。

steam的dll是用Themida保护的,其实就是Themida自己手动加载dll,目录下那个dll实际上是没用的,不过和内存里dump出来的是一样的,和Steamworks官方提供的dll好像也是一样的,这都不算什么,毕竟Alpharom十多年前就玩过了,小套路而已,但是这玩意比Alpharom强的是懂得代码虚拟化,调着真是依托shit啊。

不过GitHub好像是有几个项目能还原Themida的虚拟机,但是我本身不怎么研究脱壳加壳,要搞这个也得有点时间,而且就算搞定,也没用,因为我就没研究过steam游戏的验证机制 :),他要是稍微搞点一般steam emulator过不了的东西那我也搞不懂,到头还得研究steam验证机制谁有空啊。

ハミダシクリエイティブ只能说是凑巧,steam验证上没什么难度,但是自己开了个线程疯狂检查干掉就结束。

反正 Happy Live Show Up 官中是看着头大,对Themida加载的steam dll全部api都hook重定向到Goldberg貌似是能过steam验证的,但是返回到游戏exe里然后跳到虚拟化代码里饶了几圈就寄了,看着像是被检测到了,中途还能看到检测steam.exe是否存在的各种奇奇怪怪的检测,函数调用和返回疯狂往虚拟机里跳,从游戏exe走到,SteamAPI_RestartAppIFNecessary居然能把x64dbg的tracing的默认5w步走完还多8000多步,根个病毒一样,一般这个步数撑死都不会超过500,有多慢就可想而知了。

后面发现把他检测线程干掉了,能正常走到游戏显示启动项的地方,但是提示读取资源错误。感觉要么是有其它检测,要么是被检测到随机返回了错误的数据给游戏。

至于具体是什么问题就很难判断了,因为我这根本没steam账号,根本跑不起来这个游戏,跟踪更没可能了。还是丢回收站,右键清空回收站实在。

好吧,先别急着丢,看看能不能直接把封包结构逆出来,直接解包一步到位。

稍微去GitHub看了下QLIE的封包结构,再次对比官中的封包,看着结构很像,其实不然,官中好像只有一个封包能用garbro读出几个文件名,解包也是意义不明的数据,看着就像是改过了封包结构。

ok别管,开始跟踪解密流,跟踪的时候就明了了,官中的封包有点像是之前Malie的形式,在原格式的外面套了一层加密,通过Hook 读取文件的WinAPI和开辟堆空间的API来实现一个自己的文件系统,对封包文件的外层加密在游戏读取的时候解密掉,在游戏引擎看来就像是没加密,想着估计只是对封包的索引加密了,我先把解密后的封包文件头搞出来,然后把hash表搞出来,然后一拍脑袋发现不对,这东西整个都加密了,太狠。看着单纯提取封包的结构怕是没办法还原出来解密了。只能跟一下解密算法了。

下面就是解密算法的一部分,粗略统计应该有七次解密循环,只有第一次的xor是良心的,后面都是一坨。而且这么多重的解密应该也不像是自己写的,可能是套用了什么现成的算法或变种,当然具体不清楚,唯一清楚的是,这东西跑起来巨卡。后面刚好网上有人放了破解补丁,但是我已经对破解不感兴趣了,因为这玩意的数据加密,再加上QLIE初始化的时候会遍历全部封包的文件,导致游戏一打开卡的一坨。虽然是单核战士,并不会给你电脑卡死,除非你是单核的电脑,但是游戏确实卡。

躺平

无所谓,没人会想去理清楚,别人随手在地上画的乱七八糟的线条

好吧他确实是懂加密的,不仅加了能让游戏卡死的数据加密,还加了能让电脑累死的虚拟机,顺便开了个线程检测,专业!(虽然基本都是themida的功劳)

可是他没有QLIE的源码,这就从本质上决定了,加密的无效性。当然其实有源码也很难大改,如果真的改了那就不是QLIE了,当然这个只是站在程序的角度,如果站在系统的角度,那同样也是从本质上决定了加密的无效性。

原理

那么为什么说是从本质上决定?

就这么说吧,比如你这小子特别欠打,我邀了几个人放学后在校门口怼你,假设只有一个校门,而且围栏都通电,你只能从这一个校门出。那我搬一个凳子坐在校门口,就能蹲到你,当然你不出来可以吗?可以完全可以,那游戏也别运行了。

这个校门就像是游戏的文件接口,通常的游戏特别是Gal这种,会有封包,封包就是一种自定义的文件系统,试想你自己定义了一个封包,然后你要让游戏引擎读取,你是不是得提供函数或者接口才能读取?你不提供接口那游戏怎么读取你的封包?反正我是不知道。所以必然有这么一个接口给游戏引擎读取,不管你的封包加密有多么复杂,你也不可能带着加密的文件一路跑到结束,就算你的游戏引擎说,我一帧能跑就行,全程带着加密跑,ok完全ok,但是当你调用图形api的时候系统api的时候你还怎么带着加密文件一路跑?显然是不科学和不可能的。

通常来说一个游戏有自己私有的封包或图片文件格式,在游戏引擎里都有对应的读取封包和解析图片文件的方法,这其实是废话,就和妈妈生的一样,当然你要说克隆那我也没办法了。

所以对于一个单机的游戏,无论封包是什么加密,有多么复杂,在游戏引擎里必然有解密的方法。这和你网上下个7z压缩包要密码那可完全不一样。封包是加密了,但是解密的东西也给你了,只不过这个解密的东西是在一个exe里,并且是编译过了,全是机器码,所以就看你有没本事从这一堆机器码里找到解包的算法了。如果是你网上下个7z压缩包要密码,那就只能硬猜了。

收集

开始逆向前得多收集信息,往往这些信息可以起到事半功倍的效果。

由于我一开始把QLIE当成了C/C++开发的,一个jmp干过去,dump完了数据,准备收工,后来仔细思索发现有点不像是C/C++开发的,一查是Delphi,那这不就妥了。

来请IDR https://github.com/crypto2011/IDR

这可比IDA爽多了。很多函数名已经还原了,接着导出这些函数名和标记到IDA

可以发现封包的处理是属于TFilePack THashFile这种名称函数的,由于Delphi我甚至都没用过,只是在函数调用约定里见过Pascal调用这种,具体细节不是很清楚,不过一开始我已经分析到了QLIE循环读取封包里文件名的地方,所以从这个地方往回翻,跟踪Create Init Get这种后缀的函数大致可以推断出结构,由于我们的目的仅仅是调用接口解包,所以没必要全部分析出来,除非你想移植QLIE引擎。

QLIE Extract

那么大致解释一下,有兴趣可以自己下一个来跟踪,挺简单的。

数据和实现也已经开源,可以从这里下载到 https://github.com/Dir-A/QLIE_Extract

调用约定

首先需要了解一下调用约定
不知道是Delphi的原因还是编译器的原因,里面的所有的函数传参都很诡异,不过大致符合Pascal的传参,但又不是,第一个参数是通过eax,第二个edx, 第三个ecx,然后都通过栈传递,但是遵循Pascal的顺序。如果要C语言调用需要转换成stdcall或fastcall这种。

初始化封包

LPTPack_PackEntry THashFilePack_Create(LPTPack_InitTable pFileList, CHAR cUn0, LPTPack_Buffer pKeyFile, CHAR cUn1, CHAR cUn2, PCWCHAR lpPackName)

THashFilePack_Create 这个函数是用来初始化封包的,可以理解为打开封包,打开成功则返回TPack_PackEntry对象指针,内部的实现会去读取封包里的全部文件名。函数调用结束后紧接着就保存返回的对象。

游戏启动的时候会多次调用这个函数,初始化全部封包,保存全部的TPack_PackEntry

当然下面的结构不一定是对的,因为我只关心提取文件,没有把全部成员的具体作用都观察完

typedef struct TPack_Handle
{
	PDWORD            pTable;
	HANDLE            hFile;
	PWCHAR            lpPackName;
}*LPTPack_Handle;

typedef struct TPack_ResEntry
{
	DWORD             dwOffset;
	DWORD             dwUn0;
	DWORD             dwCompSize;
	DWORD             dwDempSize;
	DWORD             dwCompFlag;
	DWORD             dwEncFlag;
	DWORD             dwDecKey;
}*LPTPack_ResEntry;

typedef struct TPack_ResIndex
{
	PDWORD            pTable;
	LPTPack_InitTable pTPack_InitTable;
	LPTPack_Handle    pPackHandle;
	LPTPack_ResEntry* pResInfoEntry;
	PWCHAR*           pResNameEntry;
	DWORD             dwResCount;
}*LPTPack_ResIndex;

typedef struct TPack_PackEntry
{
	PDWORD            pTable;
	DWORD             dwUn0;
	PDWORD            dwUn1;
	LPTPack_ResIndex  pResIndex;
	WCHAR*            lpHashFileName;//L"GameData\\data1.hash"
	WCHAR*            lpPackFileName;//L"GameData\\data1.pack"
}*LPTPack_PackEntry;



从这里开始就可以导出全部的文件名了。
通过TPack_ResIndex的pResNameEntry和dwResCount成员就可以得到文件名和文件数量信息


读取资源

继续跟踪,观察到THashFilePack_Get这个函数,在调用之前,调用了Exists来查询文件是否存在,并且该函数只有两个参数非常简单。注意,这时候游戏已经初始化完毕,停在启动设置上,需要点开始游戏才会读取文件。

TPack_Buffer *__usercall THashFilePack_Get(TPack_PackEntry *pPackEntry, void *lpFileName)

第一个就是之前 THashFilePack_Create 返回的TPack_PackEntry,第二个就是要读取的文件名,成功则返回一个TPack_Buffer对象指针,Hook这里就可以动态dump文件,当然我们的目标不止于此。

typedef struct TPack_Buffer
{
	PDWORD            pTable;
	PDWORD            pBuffer;
	DWORD             dwSize;
}*LPTPack_Buffer;

释放资源

现在这个函数的两个参数我们都有,那么是不是意味着我们可以使用该函数来提取全部文件?确实,参数齐全,但是还有一步很重要,就是该函数返回的对象如何析构,也就是在内存中申请空间要如何释放?如果不找到释放的地方,我们就算可以成功调用该函数来提取文件,也会出现内存溢出和内存无法回收的情况。

先IDA里看看能不能捡漏,看着好像没有,那只能往上翻了,按一下X发现很多引用,还是回到x64dbg往下跟踪吧,实在不行还能下断点。不过运气比较好,第二次返回RET就看到了叫 TObject.Free

发现就是调用pTable指向的一堆函数地址的表里的个函数,调用之后buffer和size都清空了

ok至此释放的函数也找到了。

调用接口

这里我直接把THashFilePack_Get的其中一个参数的文件名改一下,然后用Garbro来解包看看返回的对象的buffer是不是对应文件名的那个文件。

改了后发现直接读取失败了,难道有什么蹊跷?仔细观察发现它的字符串是Pascal格式的,即前面有一个长度标识把这个长度也写上就正常了。但是这样每次都要计算一下长度生成Pascal格式的字符串也不是很方便。

那就继续跟踪,进来发现是封装了TFilePack_Get和TFilePack_Get0这两个函数,TFilePack_Get的第一个参数是LPTPack_ResIndex,第二个是一个序号,对应为 pResNameEntry 里的顺序,比如我要读取第一个文件,传0就可以了,第三个参数固定1,返回值就是LPTPack_Buffer了。

TFilePack_Get0则是对TFilePack_Get的封装,里面其实是用另一个函数来查序号。

好了,现在我们只需要调用TFilePack_Get传文件名和序号就可以提取全部资源了。

当然保存完成文件后别忘了释放。


结束

官中和日文版都是通用的,官方只是hook了日文版里的一些函数来实现自己的加密,所以文件应该也是差不多的。

由于新版本的QLIE是采用Unicode编码来处理文本的,那要把官中的文本搬到日文版就改两字节和复制文件的事。

反正最终分离出来,除去视频文件,就30多mb大的补丁。

标签:封包,游戏,Hook,LPTPack,加密,QLIE,读取
From: https://www.cnblogs.com/Dir-A/p/18096946

相关文章

  • Galgame 引擎免封包略谈
    Galgame引擎免封包略谈关于免封包现在的Gal引擎解包工具是比较全的,基本上很多引擎你或多或少都能找到解包的工具、源码。虽然有些并非全自动,甚至需要找密钥,但是总的来说99%的Gal引擎基解包本是不成问题的。那么对于汉化或者别的操作来说,封包,或是说让游戏读取我们修改过的文件,......
  • React-hook-form-mui(一):基本使用
    前言在项目开发中,我们选择了React+MUI作为技术栈。在使用MUI构建form表单时,我们发现并没有与antd类似的表单验证功能,于是我们选择了MUI推荐使用的react-hook-form-mui库去进行验证。但是发现网上关于这个库的使用方法和demo比较少且比较简单,并没有复杂的表单验证的demo。......
  • React Hooks的出现解决了什么问题?
    ReactHooks是React16.8版本引入的一个革命性新特性,它极大地改变了我们编写React组件的方式,并解决了许多长期存在的问题。Hooks的引入不仅简化了状态逻辑的管理,还提高了代码的可读性和可维护性。一、ReactHooks解决了什么问题?1、组件间状态逻辑复用困难在Hooks出现之......
  • React函数组件Hook
    问题:相对于类组件,函数组件的编码更简单,效率也更高,但函数组件不能有state(旧版)解决:React16.8版本设计了一套新的语法来让函数组件也可以有stateHook是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性Hook也叫......
  • 前端学习-vue视频学习011-自定义hooks
    尚硅谷视频链接axios了解了一下axios的语法importaxiosfrom'axios'exportdefaultfunction(){letdogList=reactive(['https://images.dog.ceo/breeds/pembroke/n02113023_4972.jpg'])asyncfunctiongetDog......
  • 前端React篇之React Hook 的理解及实现原理
    目录ReactHook的理解及实现原理实现原理类组件vs函数组件为什么需要HooksReactHooks解决了什么问题案例1.tsx案例2.tsx总结ReactHook的理解及实现原理ReactHook是React16.8引入的新特性,它可以让函数组件拥有类似于class组件中的state和生命周期等特性,以......
  • 【20.4】Django框架Form组件之钩子函数(Hook)
    【一】什么是钩子函数在forms组件中钩子函数(Hooks)是用来在特定事件发生时执行自定义逻辑的函数。它们提供了一种创建交互性和动态行为的方式,并可以用于处理表单的各种状态和数据。【二】常见的钩子函数【1】onInputChange当输入框的值发生变化时触发。你可以通过这个......
  • 分享一个之前开发的react键盘事件的快捷键实现,组合键,支持防抖和节流,通过自定义hooks实
    npm包地址:linkgithub地址:linkreact-khooksGettingStarted......
  • 可编辑表格中的两个列分别是用react-hook-form 和antd的inputNumber实现的,需要在开始
    可编辑表格中的两个列分别是用react-hook-form和antd的inputNumber实现的,需要在开始时间的列输入后失焦时,或者按enter键,鼠标聚焦到下一列,即结束时间,该如何设置在React项目中,要实现在一个可编辑表格中,当开始时间列输入后失焦或按下Enter键时,自动将焦点切换至结束时间列,你可以结合......
  • 滴水逆向笔记系列-win32总结10-63.IAT HOOK-64.Inline HOOK
    第六十三课IATHOOK这节课得把前面PE部分的IAT表复习好,再来做就简单多了1.IATHOOK是什么其实就是找到IAT表的位置再换成自己定义的函数,只是我们替换的函数需要和原函数的结构保持一直,比如我们要HOOKMessagebox函数,那么我们需要定义一个MyMessagebox函数,他的结构应该与Messa......