首页
社区
课程
招聘
[原创]小白如何通过大模型跑通unidbg调用sgmain生成某ckey参数
发表于: 1天前 490

[原创]小白如何通过大模型跑通unidbg调用sgmain生成某ckey参数

1天前
490
1
2
3
本篇文章仅适用于学习交流使用,如有不当,请联系删除。
 
对于逆向来说,非常的没有档次,算是提供给小白一种学习进步的方法。

一、前置工作

本人在安卓逆向方面属于小白,是跟着正己大佬《安卓逆向这档事》简单学习了一下基础知识,水平算是能够跟着大佬的攻略一步一步走下去的水平,因此本文可能存在大量解释有误的地方,欢迎大家指正。

最近恰逢春节在家无聊,同时刚好gemini 3.0 pro上线,想着通过大模型的能力,看是否能协助我完成“逆向”(本人的能力属实算不上)上的突破。之前学习过漁滒encryptR_client的生成教程,里面仅完成了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(最新版本)

一些基础知识,本文会一笔带过,大家可以通过其他文档学习,一定讲得比我好。另外遇到没讲清楚的地方,可以咨询大模型,本文涉及的所有代码,几乎全部由大模型完成。
1

二、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到方法之后,可以使用下面代码将入参和出参完整打印出来。这里就不贴代码了,大模型可以轻松搞定。

递归打印函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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已经补了?可以不用管。
2

3. getPackageCodePath

遇到第一个需要补的。
3

1
2
3
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

4

1
2
3
case "com/youku/phone/App->getFilesDir()Ljava/io/File;": {
    return ProxyDvmObject.createObject(vm, new File("/data/user/0/com.youku.phone/files"));
}

同样,相应的文件夹创建好。

5. getAbsolutePath

5

1
2
3
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

这里强烈推荐看看正己大佬的《安卓逆向这档事》第二十五课、Unidbg之补完环境我就睡(中)。很多可能的坑大佬已经介绍了怎么绕过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
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一下,看看有没有别人遇到过。github issues

1
2
3
4
5
6
7
8
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

参考安卓逆向小案例,很多本文需要补的环境,几乎都能从别人的文档中搜到。

需要注意的就是,尽量所有补的环境都加上log,方便后续debug。

1
2
3
4
5
@Override
public void callStaticVoidMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
    log.info("callStaticVoidMethod signature=" + signature);
    return ;
}

10. SPUtility2->readFromSPUnified

参考安卓逆向小案例

结论:在/data/user/0/{PKG}/files下面,,有个SGMANAGER_DATA2文件,里面JSON格式保存key-value。获取数据方法是通过arg1 + "_" + arg2作为key去取

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

问大模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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代码。需要什么参数,获取什么参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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这么说)

直接上图:
6

这个问题困扰了我很久,一直不知道怎么回事,直到最近问了gemini 3.0 pro,他给了我一个解释:

发生了什么?
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

7
继续给大模型,但是这里他判断错了,他把NR=65的方法给我了。

这里显示NR=20,svc=65。按照正己大佬《安卓逆向这档事》第二十六课、Unidbg之补完环境我就睡(下)中的解释,这里应该是JNI调用(svcNumber 不等于 0x0),但是后面又没有UnsupportedOperationException。目前超出小白的能力范畴了,使用了最简单粗暴的方法,啥也不干,直接return。(留个作业后续再看)

1
2
3
4
// MySyscallHandler中
if (NR == 20 && swi == 0x41) {
   return;
}

5. currentActivityThread

之前抄另一个文档,返回了Thread.currentThread(),后来发现这俩有区别,一个是java/lang/Thread,这里是android/app/ActivityThread,正常补就行。

6. android/app/ActivityThread->mActivities:Ljava/util/HashMap;

暂时new了个空的给过去。

四、总结

8
至此,大家应该和我一样,拿到最终的ckey了,但是因为很多地方处理并不是很完善,只能说跑出了可用的结果。另外如果想要跑encryptR_client,直接调用应该就能出,环境全部补好了的。

念念不忘,必有回响。前前后后开始->放弃->开始->放弃....重复了n次,最终借用gemini的能力跑通了,虽然在逆向学习中,自己仍然是小白,逆向的知识似乎也没有什么提升,但是通过使用大模型等能力完成多年未完成工程,心里还是十分开心的。

最后,如果还有精力,也会分享使用gemini + unidbg + ida还原算法的方法。整体来说本文难度还是比还原算法简单很多。

完结,撒花!!!

五、参考文档

unidbg调用sgmain的doCommandNative函数生成某酷encryptR_client参数
念念不忘,必有回响
unidbg实现xx请求参数算法
《安卓逆向这档事》系列
unidbg升级到最新版后 跑不起来libsgmain.so
分享9.23 mtop
安卓逆向小案例——某电影票务APP加密参数还原-Unidbg篇

最后贴一个提示词(轻喷)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
## 背景
你现在是精通安卓逆向的工程师,需要你帮助完成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. 1010110102都是初始化工作;
2. 60901初始化avmp,并拿到实例;
3. 60902是输入参数,并获取签名ckey;(重点调用)
4. 10601是获取R,可以暂时忽略。
 
## 任务
基于目前frida hook的结果,协助我完成unidbg脚本补环境的过程,其中存在大量环境监测的地方,需要你凭借经验和frida hook,拿到真机环境数据,帮助我最终拿到ckey的模拟计算。

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 1天前 被albao编辑 ,原因: 修改链接
收藏
免费 1
支持
分享
最新回复 (2)
雪    币: 17149
活跃值: (8458)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
记得ckey不是这个so生成的,楼主估计搞错了?
1天前
0
雪    币: 39
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
tDasm 记得ckey不是这个so生成的,楼主估计搞错了?
一共3个so,代码只是示例,没有放完整的,感兴趣的同学可以参考别人的文档。
20小时前
0
游客
登录 | 注册 方可回帖
返回