[译]我是如何将GTA在线模式的加载时间缩短70% (2)

[译]我是如何将GTA在线模式的加载时间缩短70%

看起来不太妙啊.我们知道大多数知名游戏都有内置保护,防止逆向工程,以远离盗版,作弊器和修改器.尽管也没怎么防住.

这里似乎有某种混淆/加密,使用花指令替换了大多数正常的指令.不过不用担心,我们只是需要在游戏运行我们关心的那块儿时转储游戏的内存. 而在执行之前, 这些指令肯定是要还原为正常指令的. 我正好手头有Process Dump, 所以我用它了, 但是有很多其他的工具也可以完成这个事儿.

#问题1: 就是… strlen?!

通过反汇编该"轻微混淆"的转储文件显示, 其中一个地址被打上标记了!这是strlen?沿调用堆栈向下找, 下一个被标记的vscan_fn,再之后,标签结束了,但我很自信它应该是.

[译]我是如何将GTA在线模式的加载时间缩短70%

这是在解析什么东西.解析啥呢?跟这些反汇编纠缠起来没完没了, 所以我决定使用x64dbg来转储一些进程的采样.在一些调试步进后, 结果出来了那就是......JSON!他们正在解析JSON.一个有6万3千个项目的10MB的JSON.

{ "key": "WP_WCT_TINT_21_t2_v9_n2", "price": 45000, "statName": "CHAR_KIT_FM_PURCHASE20", "storageType": "BITFIELD", "bitShift": 7, "bitSize": 1, "category": ["CATEGORY_WEAPON_MOD"] },

这是什么?根据一些信息,它似乎是“网络商店目录”的数据.我假设它包含你可以在GTA在线模式购买的所有可能项目和升级的列表.

这里澄清一下:我认为这些是游戏中可购买的物品,与微交易没关系.

但10MB?没事儿!使用sscanf可能不是最优的,但肯定不是那么糟糕?好吧…

[译]我是如何将GTA在线模式的加载时间缩短70%

是的,这会花一段时间......公平的讲,我之前也不知道大部分sscanf的实现都调用了strlen,所以我也不能怪罪写这个的开发者.我会假设它只是一个字节一个字节的扫描,碰到NULL后停止.

#问题2: 让我们使用哈希- ... 数组?

看起来第二个罪魁祸首是紧接着第一个被调用的. 从这个丑陋的反编译代码中能看到它们是在同一个if语句里被同等调用的.

[译]我是如何将GTA在线模式的加载时间缩短70%

所有的标签都是我起的,不知道实际调用的函数/参数是什么.

第二个问题?在解析一个项目后,将它存储在数组中(或内联的C++列表?不确定).每个条目看起来长这样:

struct { uint64_t *hash; item_t *item; } entry;

但在存储之前?它一个接一个地检查整个数组,将项目的哈希值进行比较,以检查它是否已经在列表中.大约有6万3000个项目,如果我没算错的话就是(n^2+n)/2 =(63000^2+63000)/2 = 1984531500次检查.绝大多数检查都没有用. 你已经有了唯一的哈希值为什么不使用哈希表.

[译]我是如何将GTA在线模式的加载时间缩短70%

我在逆向的时候将它命名为"哈希表",但显然它"不是一个哈希表".更绝的是.在加载JSON之前,这个"哈希数组列表"是空的.JSON里所有项目都是唯一的!他们甚至不需要检查它是否在列表中!他们甚至可以直接插入项目!用啊!真是的, 搞毛呢!?

#可行性验证(PoC)

挺好,但是没人会把我当回事, 除非我测试一下,这样我就可以给这个帖子起个骗点击的标题.

计划?写一个.dll,注入进GTA,hook一些函数,???,获利

JSON问题有点棘手,我无法实际替换他们的解析器.用一个不依赖strlen的sscanf更现实一些.但是还有一种更简单的办法.

hook strlen函数

等待一个长字符串

"缓存"它的开始位置和长度

如果在字符串范围内被再次调用的话, 返回缓存的值

例如:

size_t strlen_cacher(char* str) { static char* start; static char* end; size_t len; const size_t cap = 20000; // 如果我们已经"缓存"了这个字符串并且当前指针在它里面 if (start && str >= start && str <= end) { // 计算新的strlen len = end - str; // 快结束了, 卸载自己 // 我们不想把其它东西搞砸 if (len < cap / 2) MH_DisableHook((LPVOID)strlen_addr); // 超快的返回! return len; } // 计算实际长度 // 我们至少需要算一次这个巨大的JSON // 或者对其它字符串使用普通的strlen len = builtin_strlen(str); // 如果这确实是一个长字符串 // 保存它的开始和结束地址 if (len > cap) { start = str; end = str + len; } // 慢, 无聊的返回 return len; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdsdp.html