NX Lua 好像是一个自动化框架。
NX Lua:com.nx.* + assets/script.lr,核心都在 libassist.so。
路线很简单:先把脚本拿出来定逻辑,再决定动脚本还是动加载。script.lr 标记 enc=1。这个 enc=1 脚本包不能硬改,否则 UI 绑定会崩,所以后面我一直保持原包不动。
静态只做路线确认,真正干活在动态。Frida spawn 会死,改 attach。hook lua_load / luaL_loadbufferx,再 lua_dump 出标准 Lua 5.3 bytecode,用 unluac 反编译,核心逻辑在 @脚本/365约车.lua:输入 Edreg,设备码拼接 WEB_PT("slwyc") + board + device + hardware + model + 初始(filetime),MD5 后走云端校验。结论很直接:只要替换这一个脚本即可。
我试过最直接的 enc=0 替换脚本,UI 直接 view:null 卡死;LSPosed 会让 JNI_OnLoad 返回 JNI_ERR;嵌入 gadget 会被 linker + execmod 砍死。最后确定走内存拦截:脚本包保持 enc=1 原样,用 native 截胡 lua_load。我在 App.onCreate 最早处插一行加载 libfd.so,保证 hook 先于 AssistNative.loadLibrary(),代码:
const-string v0, "fd"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
invoke-static {}, Lcom/nx/assist/AssistNative;->loadLibrary()V
libfd.so 构造函数先关 FDSAN,然后 inline hook libassist.so 里 lua_load/luaL_loadbufferx(固定 offset,运行时 base+offset 取址),只命中 @脚本/365约车.lua 时把 reader 换成我自己的 patch 源码:
__attribute__((constructor))
static void init_fdsan_and_hook(void) {
void *h = dlopen("libc.so", RTLD_NOW);
if (h) {
void (*set_level)(int) = dlsym(h, "android_fdsan_set_error_level");
if (set_level) set_level(0);
}
// 轮询 libassist.so base,然后 inline hook lua_load / luaL_loadbufferx
}
static int name_match(const char *name) {
return name && (strstr(name, "365") || strstr(name, "365约车"));
}
int hooked_lua_load(void *L, void *reader, void *data, const char *name, const char *mode) {
if (orig_lua && name_match(name)) {
reader_state_t st = {kPatchLua, kPatchLuaLen, 0};
return orig_lua(L, (void *)(lua_reader_t)patched_reader, &st, name, "t");
}
return orig_lua ? orig_lua(L, reader, data, name, mode) : -1;
}
Lua patch 直接让注册通过:
function Get_REGS()
v7 = "注册登录"
v8 = 9999
v9 = "1"
v10 = ""
return true
end
踩坑也就一个关键点:ARM/Thumb 分支位处理错会 SIGILL,所以 hook 地址只在 thumb 模式下才加 LSB。修完后稳定:logcat 能看到 patch lua_load: @脚本/365约车.lua,注册页直接过,UI 正常,不再 view:null。