-
-
[分享]Xposed插件dump Cocos2d-x应用的lua脚本
-
发表于: 2018-4-17 11:35 9856
-
入门示例,大佬轻拍。
没有找到预览,请忍受排版。
很多安卓游戏、应用使用Cocos2d-x和lua开发,并且lua脚本都是加密保存的,根本无法直接阅读。
今天我们基于Xposed开发一个插件,来dump内存中解密过的lua脚本。
由于Xposed原生不支持native的hook,没有实现和提供相关API,所以我们需要借助其他项目的native hook代码。
创建项目
因为我们一直在使用fooXposed项目,并且不打算创建新项目,但是fooXposed不包含C++支持,所以需要手动添加C++支持。
如果你在创建新项目的时候,选择了C++选项,如下图所示,请跳过这一小节。
1. 在fooXposed项目中创建DumpLua模块
2. 从其他包含C++支持的项目中拷贝CMakeLists.txt到模块的根目录
3. 修改CMakeLists.txt如下
4. 创建JNI目录
右键点击模块=>New=>Folder=>JNI Folder
创建目录之后,在JNI Folder中创建源文件main.c,结果如下
5. Link C++ Project with Gradle
右键点击项目=>Link C++ Project with Gradle=>在Project Path中输入CMakeLists.txt文件的完整路径=>OK。
然后等待项目Build完成即可。
引用native Hook代码
因为Xposed不能支持native hook,所以我们需要使用ele7enxxh大神的Android-Inline-Hook项目,使用方法参考:https://github.com/ele7enxxh/Android-Inline-Hook。
1. 克隆下载Android-Inline-Hook代码到本地
2. 复制以下native hook代码文件到项目的JNI目录中
3. 修改CMakeLists.txt文件,在add_library中添加inlineHook.c和relocate.c的配置
4. 修改inlinehook.c文件的错误提示
a.添加include声明
#include <unistd.h>
#include <arm-linux-androideabi/asm/ptrace.h>
b. cacheflush报错,将包含cacheflush的行的代码替换如下
__builtin___clear_cache(CLEAR_BIT0(item->target_addr), CLEAR_BIT0(item->target_addr) + item->length);
lua脚本加载函数
众所周知,lua脚本的加载是通过以下函数完成的,函数的定义参考:
http://www.lua.org/source/5.0/lauxlib.c.html
http://www.lua.org/source/5.1/lauxlib.c.html
LUALIB_API int luaL_loadbuffer (lua_State *L, const char *buff, size_t size,const char *name)
其中,参数buff是lua脚本在内存的起始位置,参数size是lua脚本的的大小,参数name是脚本的名称(这个名称可能很长,也可能和buff相同)。
hook luaL_loadbuffer
按照https://github.com/ele7enxxh/Android-Inline-Hook介绍的方法hook函数luaL_loadbuffer,代码如下(请不要在意代码结构if else为了通过日志显示信息):
#define PACKAGE_NAME "target.package.name" // 目标应用的包名 #define TARGET_SO "/data/data/%s/lib/libcocos2dlua.so" // 目标应用的libcocos2dlua.so int (*origin_luaL_loadbuffer)(void *lua_state, char *buff, size_t size, char *name) = NULL; int my_luaL_loadbuffer(void *lua_state, char *buff, size_t size, char *name) { LOG_INFO("lua size: %d, name: %s", (uint32_t) size, name); // 打印lua脚本的大小和名称 // 以下代码将lua脚本写入以大小为名称的文件中,这样是有问题的。 // 其实应该以name命名文件,但是以上输出的日志会显示奇奇怪怪的名称。 // 具体细节,作为练习,自己研究吧。 // char filename[64] = {0}; // sprintf(filename, "/data/data/%s/%d.lua", PACKAGE_NAME, (unsigned int) size); // FILE *fp = fopen(filename, "w"); // if (fp) { // fwrite(buff, size, 1, fp); // fclose(fp); // } return origin_luaL_loadbuffer(lua_state, buff, size, name); } //JNIEXPORT jint JNICALL __unused JNI_OnLoad(JavaVM *vm, void* reserved){ // 使用这行代码,我的环境编译出错 JNIEXPORT jint JNICALL __unused JNI_OnLoad(JavaVM *vm) { // 我的环境只能使用这行代码,如果编译JNI_OnLoad出错,请使用上一行代码 LOG_INFO("JNI_OnLoad enter"); JNIEnv *env = NULL; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) == JNI_OK) { LOG_INFO("GetEnv OK"); char so_name[128] = {0}; sprintf(so_name, TARGET_SO, PACKAGE_NAME); void *handle = dlopen(so_name, RTLD_NOW); if (handle) { LOG_INFO("dlopen() return %08x", (uint32_t) handle); void *luaL_loadbuffer = dlsym(handle, "luaL_loadbuffer"); if (luaL_loadbuffer) { LOG_INFO("luaL_loadbuffer function address:%08X", (uint32_t) luaL_loadbuffer); if (ELE7EN_OK == registerInlineHook((uint32_t) luaL_loadbuffer, (uint32_t) my_luaL_loadbuffer, (uint32_t **) &origin_luaL_loadbuffer)) { LOG_INFO("registerInlineHook luaL_loadbuffer success"); if (ELE7EN_OK == inlineHook((uint32_t) luaL_loadbuffer)) { LOG_INFO("inlineHook luaL_loadbuffer success"); } else { LOG_INFO("inlineHook luaL_loadbuffer failure"); } } else { LOG_INFO("registerInlineHook luaL_loadbuffer failure"); } } else { LOG_INFO("dlsym() failure"); } } else { LOG_INFO("dlopen() failure"); } } else { LOG_INFO("GetEnv failure"); } LOG_INFO("JNI_OnLoad leave"); return JNI_VERSION_1_6; }
实现Xposed插件
因为属于普通的插件开发代码,本小节进行了简化描述,表达清楚意思即可。
1. 添加依赖:
compileOnly 'de.robv.android.xposed:api:82'
2. 实现接口,Hook Runtime.doLoad()方法
public class Main implements IXposedHookLoadPackage { private static final String TAG = "DumpLua"; private static final String DUMP_LUA_SO = Environment.getDataDirectory() + "/data/foo.ree.demos.dumplua/lib/libdumpLua.so"; @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpp) throws Throwable { if (!"target.package.name".equals(lpp.packageName)) return; findAndHookMethod(Runtime.class, "doLoad", String.class, ClassLoader.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String name = (String) param.args[0]; Log.i(TAG, "Load so file: " + name); if (param.hasThrowable() || name == null || !name.endsWith("libcocos2dlua.so")) { return; } Log.i(TAG, "Loading dump lua so::::::::::::::"); System.load(DUMP_LUA_SO);// 因为权限控制,在高版本的Android系统会执行失败 } }); } }
3. 配置assets/xposed_init
4. 配置meta-data
备注
只是介绍入门,介绍方法。
真正执行dump的代码被注释掉了,和文章标题描述有点差别,但是打开注释就能执行dump,但是输出的文件没有名称,会丢失很多信息。这是为了希望入门的同学自己能够动手,发现很多细节的东西。
这些代码在Android 4.4.4上测试过,运行正常。
本例只适用于32位的设备,对于64位的设备,没有测试,可以预见会存在问题。
对于寻求成品插件的同学,说声抱歉。
因为fooXposed使用最新版本的Android Studio 3.1.1开发,对于使用低版本AS的同学,可以拷贝代码到自己的工具中测试。
具体代码参考:https://github.com/fooree/fooXposed/tree/master/DumpLua
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!