首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
16
9
[原创]小白如何通过大模型跑通unidbg调用sgmain生成某ckey参数
发表于: 2026-2-23 12:31
4223
[原创]小白如何通过大模型跑通unidbg调用sgmain生成某ckey参数
albao
2026-2-23 12:31
4223
``` 本篇文章仅适用于学习交流使用,如有不当,请联系删除。 对于逆向来说,非常的没有档次,算是提供给小白一种学习进步的方法。 ``` # 一、前置工作 本人在安卓逆向方面属于小白,是跟着正己大佬《安卓逆向这档事》简单学习了一下基础知识,水平算是能够跟着大佬的攻略一步一步走下去的水平,因此本文可能存在大量解释有误的地方,欢迎大家指正。 最近恰逢春节在家无聊,同时刚好gemini 3.0 pro上线,想着通过大模型的能力,看是否能协助我完成“逆向”(本人的能力属实算不上)上的突破。之前学习过漁滒encryptR_client的<a href="elink@d4aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7I4y4U0R3H3z5o6p5$3i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9">生成教程</a>,里面仅完成了encryptR_client“补环境”过程,最终没有完成ckey的生成。当时我就搭好了架子,但是同样一直存在一些卡点,最终一直没有完成ckey部分的生成。随着最近大模型能力的提升,我通过gemini(其他大模型大家可以自测)先后完成了某讯ckey、chacha20算法还原,以及本文要说的ckey的unidbg生成。(不得不感叹当前大模型能力的强大,能帮助我这样一个完全看不懂ida伪代码的人,完成算法还原) 回到正题,开工。 1. 模拟器 or 真机 (安装frida-server) 2. GetVideo 1.3.1(随便找一个应该都行) 3. IDEA(unidbg项目) 4. python (frida hook脚本调用) 5. unidbg 0.9.8(最新版本) 一些基础知识,本文会一笔带过,大家可以通过其他文档学习,一定讲得比我好。另外遇到没讲清楚的地方,可以咨询大模型,本文涉及的所有代码,几乎全部由大模型完成。  # 二、frida hook,基础参数 需要hook的com.taobao.wireless.security.adapter.JNICLibrary下的doCommandNative方法。因为是动态加载的,所以普通的hook方法不好使,需要在BaseDexClassLoader加载class的时候进行hook。 ## Hook Logic 大致逻辑:hook BaseDexClassLoader,然后判断dexPath是否有sgmain,如果有,切换class loader之后,就可以hook到doCommandNative了。hook到方法之后,可以使用下面代码将入参和出参完整打印出来。这里就不贴代码了,大模型可以轻松搞定。 ## 递归打印函数 ```javascript function printRecursive(obj, indent, prefix) { if (indent === undefined) indent = ""; if (prefix === undefined) prefix = ""; var currentIndent = indent + prefix; // Null Check if (obj === null || obj === undefined) { console.log(currentIndent + "null"); return; } // JS Types var type = typeof obj; if (type === 'string') { console.log(currentIndent + "(JS-String) " + JSON.stringify(obj)); return; } if (type === 'number') { console.log(currentIndent + "(JS-Number) " + obj); return; } if (type === 'boolean') { console.log(currentIndent + "(JS-Boolean) " + obj); return; } // JS Array (doCommandNative 的 n 参数) if (Array.isArray(obj)) { console.log(currentIndent + "(JS-Array) Length: " + obj.length + " {"); for (var i = 0; i < obj.length; i++) { printRecursive(obj[i], indent + " ", "[" + i + "] "); } console.log(indent + "}"); return; } // Java Object if (obj.getClass) { try { var clsName = obj.getClass().getName(); if (clsName === "[B") { // === Byte Array === // 直接调用上面的终极格式化函数 console.log(currentIndent + formatByteArray(obj)); } else if (clsName.startsWith("[L")) { // === Java Object Array === // 使用反射获取长度和元素 var len = ReflectArray.getLength(obj); console.log(currentIndent + "(" + clsName + ") Length: " + len + " {"); for (var i = 0; i < len; i++) { var subElem = ReflectArray.get(obj, i); printRecursive(subElem, indent + " ", "[" + i + "] "); } console.log(indent + "}"); } else { // === Ordinary Object === console.log(currentIndent + "(" + clsName + ") " + obj.toString()); } } catch (e) { console.log(currentIndent + "[Error analysing Java Object]: " + e); } return; } console.log(currentIndent + "(Unknown Type) " + obj); } ``` ## bytearray终极格式化工具:同时输出 Hex 和 ASCII ```javascript function formatByteArray(obj) { try { var len = ReflectArray.getLength(obj); if (len === 0) return "(byte[0])"; var hexBuffer = ""; var strBuffer = ""; // 阈值:如果在 Logcat 里打印太长会截断,这里限制一下预览长度 // 如果你想看全量,可以把 limit 调大,或者去掉 var PREVIEW_LIMIT = 512; var loopLen = (len > PREVIEW_LIMIT) ? PREVIEW_LIMIT : len; for (var i = 0; i < loopLen; i++) { var b = ReflectArray.getByte(obj, i); // 1. 处理 Hex var h = (b & 0xFF).toString(16); if (h.length < 2) h = "0" + h; hexBuffer += h; // 2. 处理 String (JS 硬解码) // 判断是否为可见 ASCII 字符 (32-126) // 0x20(空格) ~ 0x7E(~) if (b >= 32 && b <= 126) { strBuffer += String.fromCharCode(b); } else { // 不可见字符用点代替,保持长度对齐,方便观察 strBuffer += "."; } } if (len > PREVIEW_LIMIT) { hexBuffer += "...(truncated)"; strBuffer += "...(truncated)"; } // 格式化输出 // 如果数据很短,单行显示;很长,分行显示 if (len < 32) { return "(byte[" + len + "]) hex: " + hexBuffer + " | str: " + strBuffer; } else { return "(byte[" + len + "])\n" + " hex: " + hexBuffer + "\n" + " str: " + strBuffer; } } catch (e) { return "format_error: " + e; } } ``` # 三、unidbg ## unidbg架子 unidbg补环境主要会涉及JNI、文件以及系统调用,这里我将3种不同环境放到不同文件下。 ### JNICLibrary ```java package com.taobao.wireless.security.adapter; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.arm.backend.Unicorn2Factory; import com.github.unidbg.file.linux.AndroidFileIO; import com.github.unidbg.linux.android.AndroidARMEmulator; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.array.ArrayObject; import com.github.unidbg.linux.android.dvm.array.ByteArray; import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger; import com.github.unidbg.linux.android.dvm.wrapper.DvmLong; import com.github.unidbg.memory.Memory; import com.github.unidbg.memory.SvcMemory; import com.github.unidbg.unix.UnixSyscallHandler; import java.io.File; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class JNICLibrary { private static final Log log = LogFactory.getLog(JNICLibrary.class); private final AndroidEmulator emulator; private final VM vm; private final Memory memory; private final Module module; private final DvmClass myjniclass; public JNICLibrary() { // syscall override AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) { @Override public AndroidEmulator build() { return new AndroidARMEmulator(processName, rootDir, backendFactories) { @Override protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) { return new MySyscallHandler(svcMemory); } }; } }; emulator = builder .setProcessName("com.youku.phone") .setRootDir(new File("unidbg-android/src/main/java/com/taobao/wireless/security/adapter/rootfs")) .addBackendFactory(new Unicorn2Factory(true)) .build(); emulator.getBackend().registerEmuCountHook(100000); emulator.getSyscallHandler().setVerbose(true); emulator.getSyscallHandler().setEnableThreadDispatcher(true); // 文件处理 emulator.getSyscallHandler().addIOResolver(new MyIOResolver()); memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); memory.setCallInitFunction(true); vm = emulator.createDalvikVM(); vm.setVerbose(true); // 补环境 vm.setJni(new MyJni()); DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/main/java/com/taobao/wireless/security/adapter/lib/libsgmainso-6.4.170.so"), true); module = dalvikModule.getModule(); vm.callJNI_OnLoad(emulator, module); myjniclass = vm.resolveClass("com/taobao/wireless/security/adapter/JNICLibrary"); } public static void main(String[] args) { JNICLibrary jnicLibrary = new JNICLibrary(); jnicLibrary.init(); } } ``` `(pkg名称,统一改成了com.youku.phone,没有使用getvideo)` ### MyJni ```java package com.taobao.wireless.security.adapter; import com.github.unidbg.linux.android.dvm.AbstractJni; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class MyJni extends AbstractJni { private static final Log log = LogFactory.getLog(MyJni.class); } ``` ### MyIOResolver ```java package com.taobao.wireless.security.adapter; import com.github.unidbg.Emulator; import com.github.unidbg.file.FileResult; import com.github.unidbg.file.IOResolver; import com.github.unidbg.file.linux.AndroidFileIO; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class MyIOResolver implements IOResolver<AndroidFileIO> { private static final Log log = LogFactory.getLog(MyIOResolver.class); @Override public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) { // 打印所有文件访问请求,无论是否处理 log.info("[MyIOResolver] ========================> File open request: " + pathname); return null; } } ``` ### MySyscallHandler ```java package com.taobao.wireless.security.adapter; import com.github.unidbg.Emulator; import com.github.unidbg.arm.backend.Backend; import com.github.unidbg.file.linux.AndroidFileIO; import com.github.unidbg.linux.ARM32SyscallHandler; import com.github.unidbg.memory.SvcMemory; import com.github.unidbg.pointer.UnidbgPointer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import unicorn.ArmConst; public class MySyscallHandler extends ARM32SyscallHandler { private static final Log log = LogFactory.getLog(MySyscallHandler.class); public MySyscallHandler(SvcMemory svcMemory) { super(svcMemory); setVerbose(true); // 可按需开启详细日志 } @Override public void hook(Backend backend, int intno, int swi, Object user) { Emulator<AndroidFileIO> emulator = (Emulator<AndroidFileIO>) user; UnidbgPointer pc = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_PC); int NR = backend.reg_read(ArmConst.UC_ARM_REG_R7).intValue(); // 打印所有系统调用,无论是否处理 log.info("[MySyscallHandler] ========================>"); log.info("syscall intno=0x" + Integer.toHexString(intno) + ", swi=" + swi + ", NR=" + NR + ", pc=" + pc); super.hook(backend, intno, swi, user); } } ``` ## 初始化 ### 初始化代码 初始化流程其他很多文档都讲清楚了的,大致如下:10101 => 10102(sgmain) => 10102(securitybody) => 10102(avmp)。 (init代码和后面avmp调用方法大家可以参考别的文章,这里就不贴了) ### 补环境 日志等级调成INFO(DEBUG太慢了),从头开始看运行流程。 #### 1. /dev/__properties__ 创建文件夹即可,其中的内容如果没有似乎不影响。 暂时不用管,测试下来如果从真机pull下来这部分文件,后面补环境会少一些步骤,如果不补,也能够成功。 #### 2. /proc/stat 目测unidbg已经补了?可以不用管。  #### 3. getPackageCodePath 遇到第一个需要补的。  ```java case "com/youku/phone/App->getPackageCodePath()Ljava/lang/String;": { return new StringObject(vm, dataAppPath + "/base.apk"); // dataAppPath = "/data/app/com.youku.phone" } ``` 同时,将base.apk复制到对应路径。(因为一开始设置了rootDir,设置的rootDir就是"/"目录,其他文件/目录可以直接往里面复制,不用处理所有文件) **对于不清楚的文件,可以到真机中去看一眼。** 这里因为用的是getvideo,所以存在一个隐形的坑,base.apk需要用getvideo解压后里面的一个Youku_xxxx.apk。后续会从该文件中读取关键安全信息。 #### 4. getFilesDir  ```java case "com/youku/phone/App->getFilesDir()Ljava/io/File;": { return ProxyDvmObject.createObject(vm, new File("/data/user/0/com.youku.phone/files")); } ``` 同样,相应的文件夹创建好。 #### 5. getAbsolutePath  ```java case "java/io/File->getAbsolutePath()Ljava/lang/String;": { return new StringObject(vm, dvmObject.getValue().toString()); } ``` #### 6. getApplicationInfo nativeLibraryDir等 常规需要补的环境,可以上网搜,或者直接问大模型。本文后续只介绍一些可能会踩坑的。 #### 7. /proc/self/status /proc/{PID}/status 这里强烈推荐看看正己大佬的<a href="elink@0ccK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7J5x3o6j5K6z5e0f1%4i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9">《安卓逆向这档事》第二十五课、Unidbg之补完环境我就睡(中)</a>。很多可能的坑大佬已经介绍了怎么绕过去。 ```java String pkg = emulator.getProcessName(); int pid = emulator.getPid(); if (pathname.equals("/proc/self/status") || pathname.equals("/proc/" + pid + "/status")) { // 返回一个包含 "TracerPid: 0" 的文件内容,表示未被调试 String statusContent = "Name:\t" + pkg + "\n" + "Umask:\t0077\n" + "State:\tS (sleeping)\n" + "Tgid:\t" + pid + "\n" + "Pid:\t" + pid +"\n" + "PPid:\t1\n" + "TracerPid:\t0\n"; // 关键行 return FileResult.success(new ByteArrayFileIO(oflags, pathname, statusContent.getBytes())); } ``` #### 8. /proc/{PID}/stat & /proc/{PID}/wchan 不懂的地方,优先google一下,看看有没有别人遇到过。<a href="elink@5d8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6*7K9r3E0D9x3o6t1J5z5q4)9J5c8Y4g2F1K9h3c8T1k6#2)9J5c8X3W2K6M7%4g2W2M7#2)9J5c8U0p5#2x3R3`.`.">github issues</a> ```java if (("/proc/" + emulator.getPid() + "/stat").equals(pathname)) { return FileResult.success(new ByteArrayFileIO(oflags, pathname, (emulator.getPid() + " (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0\n").getBytes())); } if (("/proc/" + emulator.getPid() + "/wchan").equals(pathname)) { return FileResult.success(new ByteArrayFileIO(oflags, pathname, "sys_epoll".getBytes())); } ``` #### 9. registerAppLifeCyCleCallBack 参考<a href="elink@e47K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4N6W2K9i4S2A6L8W2)9#2k6U0b7@1x3o6R3@1y4U0l9J5i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8X3c8W2N6r3q4A6L8s2y4Q4x3V1j5I4x3U0j5&6x3U0x3J5x3U0p5`.">安卓逆向小案例</a>,很多本文需要补的环境,几乎都能从别人的文档中搜到。 需要注意的就是,尽量所有补的环境都加上log,方便后续debug。 ```java @Override public void callStaticVoidMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { log.info("callStaticVoidMethod signature=" + signature); return ; } ``` #### 10. SPUtility2->readFromSPUnified 参考<a href="elink@660K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4N6W2K9i4S2A6L8W2)9#2k6U0b7@1x3o6R3@1y4U0l9J5i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8X3c8W2N6r3q4A6L8s2y4Q4x3V1j5I4x3U0j5&6x3U0x3J5x3U0p5`.">安卓逆向小案例</a>。 `结论:在/data/user/0/{PKG}/files下面,,有个SGMANAGER_DATA2文件,里面JSON格式保存key-value。获取数据方法是通过arg1 + "_" + arg2作为key去取` ```java case "com/taobao/wireless/security/adapter/common/SPUtility2->readFromSPUnified(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": { String arg1 = varArg.getObjectArg(0).getValue().toString(); String arg2 = varArg.getObjectArg(1).getValue().toString(); String key = arg1 + "_" + arg2; System.out.println("KEY==> "+ key); JSONObject data = JSONObject.parseObject("{\"dynamicreid_dynamicreid\":\"xxxx\",\"dynamicrsid_dynamicrs这里还有很多,直接省略了\"}"); String result = data.getString(key); System.out.println("data ==> " + result); if (result != null) { return new StringObject(vm, result); } return null; } ``` #### 11. JNIBridge->registerInfoCallback 返回第二个参数。 #### 12. UserTrackMethodJniBridge->utAvaiable 返回1。 #### 13. SG_INNER_DATA & SG_USER_DATA 1. SG_INNER_DATA: 从真机拿到文件,放到rootfs对应路径。这个文件会自动生成一个,替换他。 2. SG_USER_DATA: 没看到,暂时空着没管。 #### 14. SPUtility2->saveToFileUnifiedForNative 看起来是存到刚才的JSON里面,当你补了SG_INNER_DATA这个文件之后,这个方法就用不到了,所以不补。 #### 15. UserTrackMethodJniBridge->addUtRecord 返回1就行。感兴趣可以将参数都打印出来看看,应该是用来记录设备行为的。 #### 16. SGPluginExtras->slot get方法时将值保存下来,set的时候返回值。 ### 小结 至此,so的初始化就结束了,前面如果遇到乱七八糟的报错,很有可能是文件/目录访问没有处理好,相应的文件和目录都存在的情况下,基本没有什么大坑。 ## avmp计算ckey ### avmp初始化 & ckey计算 `流程:通过60901初始化avmp,然后60902获取ckey` 经过测试,60901获取到avmp instance之后,应该可以复用。 `_str输入为:ccode=01010101&client_ip=192.168.1.1&client_ts=1770000000&utid=xxxx&vid=XMjk4ODAyMzIyOA==` ### 补环境 #### 1. avmp补环境 上面已经讲了base.apk存在坑,如果没有踩这个坑,这里简单补几个环境应该就能完成avmp初始化了。后面很多不太常见的补环境,基本都是依靠gemini帮我补完的,大家也可以试试请教一下大模型。 #### 2. [B->getClass()Ljava/lang/Class; 问大模型: ```java case "[B->getClass()Ljava/lang/Class;": { // 获取调用这个方法的对象,也就是 byte 数组本身 Object value = dvmObject.getValue(); // 方法1:如果 value 本身就是 byte[],可以直接获取它的 Class 对象 if (value instanceof byte[]) { Class<?> clazz = value.getClass(); // 将 Java 的 Class 对象转换为 unidbg 的 DvmObject // 这里可以直接使用 ProxyDvmObject.createObject 来封装 byte[] byteArray = (byte[]) value; log.info("=== [B->getClass() 调用 ==="); log.info("Byte数组长度: " + byteArray.length); log.info("Byte数组内容(hex): " + bytesToHex(byteArray)); log.info("Byte数组内容(ASCII): " + new String(byteArray).replaceAll("[^\\x20-\\x7E]", ".")); log.info("========================="); return ProxyDvmObject.createObject(vm, clazz); } // 方法2:或者直接返回一个代表 byte[] 类型的 Class 对象 // 这种方式更直接,不依赖于实际对象 Class<?> byteArrayClass = (new byte[0]).getClass(); return ProxyDvmObject.createObject(vm, byteArrayClass); } ``` #### 3. DeviceInfoCapturer->doCommandForString 获取设备信息,大模型给了hook代码。需要什么参数,获取什么参数。 ```javascript function doCom(i) { Java.perform(function() { Java.enumerateClassLoaders({ "onMatch": function(loader) { if (loader.toString().indexOf("libsgmain.so") >= 0 ) { Java.classFactory.loader = loader; // 将当前class factory中的loader指定为我们需要的 console.log("loader = ",loader.toString()); } }, "onComplete": function() { console.log("success"); } }); let DeviceInfoCapturer = Java.classFactory.use("com.taobao.wireless.security.adapter.datacollection.DeviceInfoCapturer"); var result = DeviceInfoCapturer.doCommandForString(i); console.log("result ==> " + result) }) } ``` #### 4. ArtMethod 结构体检查(gemini这么说) 直接上图:  这个问题困扰了我很久,一直不知道怎么回事,直到最近问了gemini 3.0 pro,他给了我一个解释: ```markdown 发生了什么? 1. SO 想要获取设备的 IMEI,所以它去拿 TelephonyManager.getDeviceId() 的方法 ID。 2. Unidbg 作为一个模拟器,为了方便管理,把方法的 Hash 值(0x63bb9035)作为 MethodID 返回给了 SO。 3. 关键来了:在真实的 Android 系统中,MethodID 其实是一个内存指针,指向底层的 ArtMethod 结构体。libsgmain 非常狡猾,它拿到这个 ID 后,并没有立刻去调用方法,而是把它当成内存指针,硬加上偏移量(0x63bb9035 + 5 = 0x63bb903a),试图去读取内存里面的方法的 Access Flags 或者入口点,以此来判断这个方法有没有被 Frida 或 Xposed Hook 掉! 4. 因为 0x63bb9035 只是 Unidbg 算出来的一个 Hash 数字,这块内存地址在 Unicorn 引擎里根本没有被映射(Unmapped),所以 SO 一读就直接触发了内存访问异常,导致进程崩溃。 解决方案 要跑通这个逻辑,我们需要做两步:第一步是“骗”过它的 Hook 检测,第二步是给它返回一个假的 IMEI。 ``` 这个解释是否正确,我也不清楚,各位大佬如果有懂的可以评论回复。通过gemini给的方法,我发现压根不用在对应地址写入数据,只需要把内存空间开辟出来,读取内存不报错,这里就可以跑过去了。 当然,有可能是这里处理地不够好,导致后面生成的ckey长度会比frida hook出来的短上一些,好在可以正常使用。 #### 5. svc number: 65  继续给大模型,但是这里他判断错了,他把NR=65的方法给我了。 这里显示NR=20,svc=65。按照正己大佬<a href="elink@b87K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7J5x3o6j5@1y4U0b7H3i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9">《安卓逆向这档事》第二十六课、Unidbg之补完环境我就睡(下)</a>中的解释,这里应该是JNI调用(svcNumber 不等于 0x0),但是后面又没有UnsupportedOperationException。目前超出小白的能力范畴了,使用了最简单粗暴的方法,啥也不干,直接return。(留个作业后续再看) ```java // MySyscallHandler中 if (NR == 20 && swi == 0x41) { return; } ``` #### 5. currentActivityThread 之前抄[另一个文档](https://bbs.kanxue.com/thread-268927-1.htm),返回了Thread.currentThread(),后来发现这俩有区别,一个是java/lang/Thread,这里是android/app/ActivityThread,正常补就行。 #### 6. android/app/ActivityThread->mActivities:Ljava/util/HashMap; 暂时new了个空的给过去。 # 四、总结  至此,大家应该和我一样,拿到最终的ckey了,但是因为很多地方处理并不是很完善,只能说跑出了可用的结果。另外如果想要跑encryptR_client,直接调用应该就能出,环境全部补好了的。 **念念不忘,必有回响**。前前后后开始->放弃->开始->放弃....重复了n次,最终借用gemini的能力跑通了,虽然在逆向学习中,自己仍然是小白,逆向的知识似乎也没有什么提升,但是通过使用大模型等能力完成多年未完成工程,心里还是十分开心的。 最后,如果还有精力,也会分享使用gemini + unidbg + ida还原算法的方法。整体来说本文难度还是比还原算法简单很多。 完结,撒花!!! # 五、参考文档 <a href="elink@256K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7I4y4U0R3H3z5o6p5$3i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9">unidbg调用sgmain的doCommandNative函数生成某酷encryptR_client参数</a> <a href="elink@a96K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8V1A6G2K9r3&6Q4y4h3k6x3k6h3&6G2L8W2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1k6V1k6i4c8S2K9h3I4K6i4K6u0r3x3e0x3^5x3U0R3H3z5e0R3J5">念念不忘,必有回响</a> <a href="elink@782K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8V1A6G2K9r3&6Q4y4h3k6x3k6h3&6G2L8W2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1k6V1k6i4c8S2K9h3I4K6i4K6u0r3x3e0t1&6y4e0M7J5x3U0p5%4">unidbg实现xx请求参数算法</a> <a href="elink@dd2K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7J5x3o6j5@1y4U0b7H3i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9">《安卓逆向这档事》系列</a> <a href="elink@382K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6*7K9r3E0D9x3o6t1J5z5q4)9J5c8Y4g2F1K9h3c8T1k6#2)9J5c8X3W2K6M7%4g2W2M7#2)9J5c8U0p5#2x3R3`.`.">unidbg升级到最新版后 跑不起来libsgmain.so</a> [分享9.23 mtop](https://bbs.kanxue.com/thread-268927-1.htm) <a href="elink@c17K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4N6W2K9i4S2A6L8W2)9#2k6U0b7@1x3o6R3@1y4U0l9J5i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8X3c8W2N6r3q4A6L8s2y4Q4x3V1j5I4x3U0j5&6x3U0x3J5x3U0p5`.">安卓逆向小案例——某电影票务APP加密参数还原-Unidbg篇</a> 最后贴一个提示词(轻喷) ``` ## 背景 你现在是精通安卓逆向的工程师,需要你帮助完成unidbg调用一个so的签名。 现在我已经通过frida,hook到了签名函数ckey的输入和输出,现在我希望你帮助我使用unidbg模拟so的环境,并成功计算出ckey。其中我可以给你ida解析的so的伪代码,以及帮你使用frida hook更多参数供你分析。其他ida的脚本,我也可以尝试帮你使用。(我是小白,如果需要ida插件,需要详细说明一次) ## 要求 1. unidbg补环境时,对于常见环境,可以直接补充,对于不常见或者拿不准的地方,可以使用frida hook到相应参数再补充。 2. 每次回复,可以简单解释你的分析,不要太多,因为我看不太懂,我不需要知道原理,我只需要协助你运行代码,帮你获取so的伪代码。 ## 技能 1. 可以让我协助你hook函数参数,inline hook等各种frida支持的方式。 2. 可以让我给你提供so的伪代码。 3. 可以让我使用ida的插件协助分析(注意,我是小白,如果使用这个技能,需要你详细指导我一次使用方法)。 4. 其他可能有助于你分析的方法,可以教我使用。 ## 目前进展 ### unidbg脚本 所有unidbg脚本 balabala ### frida hook脚本 hook脚本 ### frida hook结果 hook结果 ### 简单解释现有hook结果 1. 10101、10102都是初始化工作; 2. 60901初始化avmp,并拿到实例; 3. 60902是输入参数,并获取签名ckey;(重点调用) 4. 10601是获取R,可以暂时忽略。 ## 任务 基于目前frida hook的结果,协助我完成unidbg脚本补环境的过程,其中存在大量环境监测的地方,需要你凭借经验和frida hook,拿到真机环境数据,帮助我最终拿到ckey的模拟计算。 ```
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-2-23 13:42 被albao编辑 ,原因: 修改链接
#逆向分析
收藏
・
16
点赞
・
9
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
夜微凉的流伤
非常支持你的观点!
2天前
Ayuer
为你点赞!
2026-9-29 19:39
kufei
谢谢你的细致分析,受益匪浅!
2026-8-10 21:49
jjjo
感谢你的贡献,论坛因你而更加精彩!
2026-6-18 17:59
n_1ng
你的帖子非常有用,感谢分享!
2026-5-16 00:14
烬奇小云
谢谢你的细致分析,受益匪浅!
2026-3-6 10:23
qqizai
期待更多优质内容的分享,论坛有你更精彩!
2026-2-28 09:38
拾海
这个讨论对我很有帮助,谢谢!
2026-2-25 20:38
git_47499test-look
谢谢你的细致分析,受益匪浅!
2026-2-23 12:39
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
5
)
tDasm
雪 币:
18345
活跃值:
(9613)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
710
粉丝
23
关注
私信
tDasm
2
楼
记得ckey不是这个so生成的,楼主估计搞错了?
2026-2-23 13:27
0
albao
雪 币:
39
活跃值:
(110)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
3
粉丝
6
关注
私信
albao
3
楼
tDasm
记得ckey不是这个so生成的,楼主估计搞错了?
一共3个so,代码只是示例,没有放完整的,感兴趣的同学可以参考别人的文档。
2026-2-24 09:32
0
CheRevir
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
90
粉丝
0
关注
私信
CheRevir
4
楼
6
2026-2-27 17:29
0
@NoBody
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
5
回帖
41
粉丝
4
关注
私信
@NoBody
5
楼
你好,我自己做了一个二进制静态动态结合的创新的方案(我研究过学术界关于二进制跟踪的很多论文),可以记录ios程序起始到结束堆内存读写和函数调用(普通的类似stalker的方法太慢了是无法实现的),速度只减少50%。不过最近不做逆向了,800块出售逆行逆向源码。WX:jiqizaisikao
2026-3-4 08:31
0
七浪
雪 币:
32
活跃值:
(115)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
24
粉丝
3
关注
私信
七浪
6
楼
大模型 + unidbg 这条路径写得很清楚,补环境步骤对小白友好,感谢分享。
2026-4-7 10:39
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
albao
1
发帖
3
回帖
10
RANK
关注
私信
他的文章
[原创]小白如何通过大模型跑通unidbg调用sgmain生成某ckey参数
4223
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部