S2BinLib - 一个用Rust编写的多功能内存工具库

为了给插件提供方便的内存工具(pattern scan,找虚表等等),同时保证兼容性,于是开发了S2BinLib这个子项目。这个项目最大的重点主要是大部分内存查找操作是通过读取二进制文件实现的,而不是在内存的模块里。这样做的好处是可以防止别的框架已经hook了一些函数,导致特征码改变,无法扫描到。或者是已经替换了虚表里的一个函数指针,等等。S2BinLib完全从二进制文件里查找,并且提供简便的方法从真实的内存地址转到模块的RVA,再转到文件偏移读取。

这个Rust库可以同时导出静态链接和动态链接的版本,可以按需编译使用。最开始设计是只支持静态编译的,但是后面发现除非显式声明,在Linux上的函数会默认导出,这样会导致同时在两个项目中使用时,如果两个项目同时加载到了一个进程,会导致ELF符号劫持,不太优雅。

项目地址:swiftly-solution/s2binlib

初始化

从初始化开始,s2binlib提供了简单的初始化方法,只需要一行代码就可以设置好提前需要的路径。

s2binlib_initialize("/home/csgo/cs2server/game", "csgo");

也可以使用 s2binlib_initialize_with_os 方法。理论上来说所有不涉及到内存地址和模块的函数都是跨平台兼容的,这也是为什么dumper完全可以实现同一个平台读取两个平台的二进制。

如果你在使用Metamod,还需要重新定位server模块的地址。因为Metamod会在启动时注入自己的同名server.dll,从而劫持DLL加载路径,而实现加载插件。

 GET_V_IFACE_ANY(GetServerFactory, g_pSource2Server, ISource2Server, SOURCE2SERVER_INTERFACE_VERSION);
 GET_V_IFACE_CURRENT(GetEngineFactory, g_pEngineServer2, IVEngineServer2, SOURCE2ENGINETOSERVER_INTERFACE_VERSION);
 s2binlib_set_module_base_from_pointer("server", g_pSource2Server);
 s2binlib_set_module_base_from_pointer("engine2", g_pEngineServer2);

Pattern Scan(内存签名)

Pattern scan是通过内存特征而在版本不同的模块中找到同一个函数的方法。主要是识别函数开头的几个指令字节,并且把立即数替换成wildcard,这样可以保证稳定性。然而,如果这个函数在模块中被inline hook了,开头往往会被替换成一个jump指令,导致特征失效。s2binlib可以在原始二进制文件里进行扫描,并且自动从RVA转为真实内存地址返回。

此外,s2binlib还为支持simd的cpu提供了simd指令支持,使扫描更加迅速。

void* result;
s2binlib_pattern_scan("server", "01 02 03 AA BB CC ? ? DD", &result);

这行代码可以在server模块中(可跨平台)扫描这个内存签名。

返回值详见文档。

寻找 VTable

在RTTI符号没有被strip的情况下,我们可以从type descriptor name中获取虚表的真实名称和继承信息。s2binlib可以自动mangle虚表名,并找到type descriptor的位置,从而定位到虚表。API调用也很简单。

void* vtable_addr;
s2binlib_find_vtable("server", "CBaseEntity", &vtable_addr);

像这样的功能还有很多,比如从二进制文件中的虚表读取并转换为真实指针,从而防止运行时虚表指针被替换等。

找特殊 NetworkStateChanged 函数

在Source 2的SchemaSystem中,有些特殊Network字段会有自己的虚表。他们通过宏生成自己的NetworkStateChanged函数,并且位置大多数都不一样。为了找出所有这些函数,s2binlib内置了搜索函数,可以自动定位到这些函数(通过指令特征比对)。

找出所有 Xref

通过rust的iced反汇编引擎,可以解析整个二进制的xref指令。这样可以帮助我们定位很多信息,比如一个虚表在哪里使用等等。但是这个操作比较耗时,所以s2binlib提供了一个函数供你手动dump,并且内置缓存,以供后续使用。

Comments