首页
社区
课程
招聘
[分享]Xposed插件dump Cocos2d-x应用的lua脚本
发表于: 2018-4-17 11:35 10161

[分享]Xposed插件dump Cocos2d-x应用的lua脚本

2018-4-17 11:35
10161

入门示例,大佬轻拍。

没有找到预览,请忍受排版。

很多安卓游戏、应用使用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项目,使用方法参考:193K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6W2L8r3f1%4k6h3&6^5P5r3S2Q4x3V1k6m8L8X3c8J5L8$3W2V1i4K6u0V1d9h3&6D9K9h3&6W2i4K6u0V1d9r3!0G2K9#2!0q4x3#2)9^5x3q4)9^5x3R3`.`.

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脚本的加载是通过以下函数完成的,函数的定义参考:

        154K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3I4#2j5g2)9J5k6h3!0J5k6#2)9J5c8Y4y4G2N6i4u0U0k6g2)9J5c8U0g2Q4x3X3f1H3i4K6u0r3L8r3q4#2P5r3I4A6j5W2)9J5k6h3y4Q4x3X3g2Z5N6r3#2D9

        975K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3I4#2j5g2)9J5k6h3!0J5k6#2)9J5c8Y4y4G2N6i4u0U0k6g2)9J5c8U0g2Q4x3X3f1I4i4K6u0r3L8r3q4#2P5r3I4A6j5W2)9J5k6h3y4Q4x3X3g2Z5N6r3#2D9

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

按照28eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6W2L8r3f1%4k6h3&6^5P5r3S2Q4x3V1k6m8L8X3c8J5L8$3W2V1i4K6u0V1d9h3&6D9K9h3&6W2i4K6u0V1d9r3!0G2K9#2!0q4y4q4!0n7b7W2)9^5b7W2!0q4y4#2!0n7b7W2)9^5c8q4!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4W2)9&6y4W2!0n7z5g2!0q4y4W2!0n7x3#2)9&6y4h3S2G2L8$3E0Q4c8e0g2Q4z5o6N6Q4b7V1c8Q4c8e0k6Q4z5e0g2Q4b7U0m8D9N6h3q4x3i4K6g2X3L8r3!0S2k6r3u0#2k6X3k6W2M7W2!0q4c8W2!0n7b7#2)9^5b7#2!0q4y4q4!0n7b7W2!0m8x3#2!0q4y4#2!0m8x3q4)9^5x3g2!0q4y4g2!0m8y4W2)9^5x3W2!0q4y4q4!0n7z5q4)9^5b7W2!0q4c8W2!0n7b7#2)9^5z5q4!0q4z5q4!0m8c8W2!0n7y4#2!0q4y4q4!0n7z5q4)9^5c8q4!0q4z5q4!0m8y4W2)9^5x3g2!0q4y4g2)9&6b7#2!0m8z5q4!0q4y4W2)9^5y4q4)9^5c8W2!0q4y4q4!0n7b7W2!0m8x3#2!0q4y4#2!0m8x3q4)9^5x3g2!0q4y4#2!0n7b7W2)9&6x3#2!0q4y4W2)9&6c8g2)9^5y4r3W2X3 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的同学,可以拷贝代码到自己的工具中测试。

具体代码参考:416K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6X3L8$3!0J5k6h3g2Q4x3V1k6X3L8$3!0j5M7r3!0K6k6h3c8Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3c8s2g2E0M7p5I4#2j5b7`.`.






[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回