找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2614

积分

0

好友

346

主题
发表于 2 小时前 | 查看: 4| 回复: 0

首先就是进行抓包了,下面是抓到的数据包:

POST /mobile/login/pwd/v3 HTTP/2
host: passportws.ximalaya.com
cookie: 1&_device=android&386501be-0e5c-3773-8b4b-d2f40c257a9a&9.4.52;channel=and-d3;impl=com.ximalaya.ting.android;osversion=33;fp=009317657x2222q22264v0500q00000220020000000000012000000000000;device_model=Pixel+7;XIM=;c-oper=%E6%9C%AA%E7%9F%A5;net-mode=WIFI;res=1080%2C2219;AID=MWFiYjhmN2U4OGM4MWY1Mg==;manufacturer=Google;XD=SWVzJyp9dAqYNR2NV/TpWj32w93g1JQRPZp94Qh4YkOIDrTzHvhcg7DkwZddPJEEo8Spj+ypYaAptOHvZ34ujAtl7b57bnLDLTGMOQXm6dRZdGAIpQYNJ+aXMKDZ1dOR;umid=4051f75d0861f70a9723a837b71ae290ia;xm_grade=0;specialModeStatus=0;
cookie2: $version=1
accept: */*
user-agent: ting_9.4.52(Pixel+7,Android33)
x-tk: TACaa2BczhlAb4OXDdzi0vS9AwlepoCuKX6lSN7MhFEYXGnikMYJO-ycVZWJ81G8Ka6U0hO-anu4NP90d1yExsZuYTs68ljb20ueGltYWxheWEudGluZy5hbmRyb2lkITEuMy4yNyE5LjQuNTIuMyFiPXBhc3Nwb3J0JnM9bG9naW4mdT0w
content-type: application/json; charset=utf-8
content-length: 1338
accept-encoding: gzip

{"password":"Tv97ethyYCbW1cu28wKT6LoYasOlozs1bW6XkMsdesPTbWrcUxP0OzDFsBK+56J9V6NQCKCoxEpa\nsjbAd6P7rdGrUMVFQ9VFuJhucxTbZn3FtCT4DdxW0okyw1/7CHxVVo6SESazZqCvdYX6JduejQzg\nsHSx/uilKXMvMymhTmY\u003d\n","fdsOtp":"{\"captcha_id\":\"3723312ce42a04b5c0b40e605a882037\",\"lot_number\":\"f4cc6449828648b4a7482fcb187e0fe7\",\"pass_token\":\"29e78908983de432362042dfa8709d798254c6a5788b046b598bb849a162bab3\",\"gen_time\":\"1772978512\",\"captcha_output\":\"u9reFROUhQ6l5CNk-Y40DVpFm0A5jhpxZAMu8kiKIQFHmCtbVGvr_4a-HewecbuH_vgp4hEw_THRvGykOEhBHfIDOK2mngR_rxYH5LSZFntK0h-JrAso3IZc_a06v3Roo7cw7GARksy9__4dkEt5yro5gqzZbcZBGD1ByeVUK2qxlCiTZXqacspeuZTwmJgbSGLUZt8tnLAcnutND4K1rlpmk6D4NkEqjwyGz_3O_Sosrapf9KCpJiT76IJPprj0sdbRpmHzaV3dbltgA7WmUSqU-GmZly9HWALit1_qljzgrHq6TGb6C60noDZJ82qq5Ainym8HU7Ug4CTO9AO51GliCpeyVckKGNxA0EKZAi9LpVUIW5HHZDUs8-dG87PP43h4RbsyfL3fNYGFh5eHgLNFgg7SKEY9iNhCghPU7PJwWJ6QvwpqDOEqRHZK_yMj_KS54z8drWeu38v5Zr-K0Os3ABsdHcOCKad7JrKl08bl6NsWAWIj5J8ek9I7SbnsF-OBspi0BHAdJLO2snYWJYfTp7CuHu95XRIHzfyZaOI\u003d\"}","signature":"90cf350f2fd44f466325a7210cc34fa7553dc60d","nonce":"0-8D0B9F3600C47d912f9545b45cf0ef55d34f40b311136eb6adf183382af41e","account":"PJ+lxL+0CidJt+eJXvLypUoVliyTsh1RjQQ7DBuTsmqBKUg4qcpMf85TSUjZYT1ChwVzjyBDkHcJ\ngBM0gSRznapMGVfAx3Q4ewy8GuvzdWKdfO8h30HZD4+lr92cp6tcPGY9pER8VHdrKWVW8iyR9gbs\n4YINBi3EWDB//KWb4qI\u003d\n"}

接下来需要定位到登录函数调用的位置,可以通过关键字段的搜索、或者是hook函数打印调用堆栈

这里我直接hook了 HashMap 来打印堆栈,找到登录点:

Java.perform(function(){
    console.log("
  • Script loaded. Waiting for 'signature' to be put into a Map or JSONObject...");     var log = Java.use("android.util.Log");     var HashMap = Java.use("java.util.HashMap");     HashMap.put.implementation = function(key, value){         if(key !== null){             var keyStr = key.toString();             if(keyStr === 'signature'){                 console.log("[+] 找到拼接 signature, 即将打印调用堆栈....");                 console.log(log.getStackTraceString(Java.use("java.lang.Throwable").$new()) + '\r\n');             }         }         return this.put(key,value);     } })
  • 定位请求体组装位置:

    [Pixel 7::喜马拉雅 ]-> [+] 找到拼接 signature, 即将打印调用堆栈....
    java.lang.Throwable
            at java.util.HashMap.put(Native Method)
            at com.ximalaya.ting.android.loginservice.LoginRequest$4$1.b(Unknown Source:386)
            at com.ximalaya.ting.android.loginservice.LoginRequest$4$1.a(Unknown Source:365)
            at com.ximalaya.ting.android.loginservice.d.b$2.onSuccess(Unknown Source:73)
            at com.geetest.captcha.q.run(Unknown Source:1)
            at android.os.Handler.handleCallback(Handler.java:942)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loopOnce(Looper.java:201)
            at android.os.Looper.loop(Looper.java:288)
            at android.os.XimaCrashHandler$1.run(Unknown Source:232)
            at android.os.Handler.handleCallback(Handler.java:942)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loopOnce(Looper.java:201)
            at android.os.Looper.loop(Looper.java:288)
            at android.app.ActivityThread.main(ActivityThread.java:7898)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

    调用堆栈与请求体组装代码

    这里重定义了两个 a 函数,除了从调用链上判断使用的是第一个a函数外,还可以通过frida的 overload 来判断使用的是哪个函数。

    signature拼接位置代码

    这里就是 signature 进行拼接的位置,需要通过 LoginRequest.b 进行跟进,最后发现调用的是 LoginEncryptUtila 当中的 WTWEctUfLf 函数。

    LoginEncryptUtil native方法声明

    通过函数声明可以看到这是个 native 函数,属于 liblogin_encrypt.so 这个库,接下来就需要对这个库当中的这个 native 函数进行分析了。

    so层分析

    进入so当中的 WTWEctUfLf 通过 CFG 可以查看到这个函数是被控制流平坦化了。

    OLLVM混淆的CFG图

    Unidbg模拟执行

    这里想试试用 Unidbg 模拟执行一下看看,能不能找到一些线索,这里传入的参数需要注意一下,通过 jadx 的反编译可以看到传入了3个参数:

    a方法的参数定义

    分别是上下文 context,布尔值 z 以及拼接后的参数 sb.toString。这里已经知道了参数一和参数三的形式了,但是参数二是 true 还是 false 依旧不清楚。

    那么这里可以直接对这个 a 方法进行 hook,看看传入的是什么,hook脚本如下:

    Java.perform(function(){
        var LoginEncryptUtil = Java.use("com.ximalaya.ting.android.loginservice.LoginEncryptUtil");
        LoginEncryptUtil.a.overload('android.content.Context', 'boolean', 'java.util.Map').implementation = function(ctx,z ,map){
            console.log("    [param2] z : " + z + "\r\n");
            return this.a(ctx,z,map);
        }
    });

    最后打印的是 false

        [param2] z : false

    那么此时就知道了参数二。此外为了确保模拟执行 WTWEctUfLf 的逻辑没有错误,这里将传入的 sb.toString() 以及返回值都 hook 出来,用作模拟执行的校验,使用 Frida 框架进行hook:

    var WTWEctUfLf_offset = 0x39BC;
    function hook_login_enc(){
        var liblogin_encrypt = Process.findModuleByName("liblogin_encrypt.so");
        console.log("[+] 找到liblogin_encrypt的基址: " + liblogin_encrypt.base);
        var WTWEctUfLf_addr = liblogin_encrypt.base.add(WTWEctUfLf_offset);
        console.log("[+] 目标函数地址: "+WTWEctUfLf_addr);
    
        Interceptor.attach(WTWEctUfLf_addr,{
            onEnter: function(args){
                var jstringArg = args[4];
                console.log("args_4 : " + args[4]);
                if(!jstringArg.isNull()){
                    var jniEnv = Java.vm.getEnv();
                    var cString = jniEnv.getStringUtfChars(jstringArg, null);
                    var strValue = cString.readUtf8String();
                    console.log("[+] 传入的明文 (sb.toString()):\n\n" + strValue + "\n");
                    jniEnv.releaseStringUtfChars(jstringArg, cString);
                }
            },
            onLeave: function(retval) {
                if (!retval.isNull()) {
                    var jniEnv = Java.vm.getEnv();
                    var cString = jniEnv.getStringUtfChars(retval, null);
                    console.log("[+] 返回的密文 (signature): " + cString.readUtf8String());
                    jniEnv.releaseStringUtfChars(retval, cString);
                }
                console.log("======================================\n");
            }
        });
    }
    hook_login_enc();

    最终得到的数据如下:

    [+] 传入的明文 (sb.toString()):
    
    account=B8vZmyyyzbNpd7bP+vNYYYGL8Idvddn85ENtGFFxj5An9vCQjv3yXNcli6I24KyRBje/9bADfYpn
    a4FywwajYUMmu6ef25+tTPTwaDI1Jq7I5hBbS3PNi0E079ic8m/KP+lD3GG8GXjF1woNcY0UphWE
    +p4LXqBoZv1wa1FE9Zo=
    &fdsOtp={"captcha_id":"3723312ce42a04b5c0b40e605a882037","lot_number":"6ac21b439ee744cd86b7c462eddc3930","pass_token":"4c82b272afc4b59df1057cf66838a7bd3327afdfefe1be2456580fbcb0de869d","gen_time":"1772978707","captcha_output":"2Z76G2CapbcJxb3z0NRv6IFLTIZJ15mHQYRlVlkywBquuBg9auDb78p_hyS26NaSWJddIYSKQ83ixvuXQfCJWeMiXfURka9GYWuWyS6TkZQEEszgpPITemgwA5BntSqWd54TlHATrcJLgCSPoh81t0rr3Wgz2rJiU4_eTqL7aekJxSCVJ6mADxUzOUMC4q8J_YuyefL7SJlKGx_RXe6PZZYkOt2LDMl-E7qRTMYQlp6r2GCVcGQNJheZovQ1ZzFgzSX_PDFuq3Fm2Ir3y-nI5By57Kar1ohGJv8jGZtE5WEIFV2hoIlC0brWStgKwrTrXmJjnxBh_3S3ak1Xs1Zfaeaov4hPlfGpwW-Lh8OdX6WgAtxb7TsN0XyTpX3j-r8HKz7D71M4UFxSuhgl3OdrCjR5s--JcFybjIy6ANxjR7VlsxWJjyyxJ1x8M331UFD6wksJZjJpKX7LoWgFS48v1_DVxDAWmE4xis6YQYfIumMXS0PicjaxMcSLFyBEreWO4IiYHafv_7GVj5Qns-KihnUBgFLeBsG4b3CvU9-zvzg="}&nonce=0-CCFB9B6F4E55df4ea842b75758d257361290d8f504ec1a1633b85d3010803b&password=QpHGc4OjScTQRP9PTdYxn7ABdCmwS1JqfsBD00gX5UJ0/CSZ8HtNB/K4GrERFODfqKttLqI6Rkoi
    Fn15KquI1pMytM8/D9yvvLbg8FpWr2E2JKoMWrZ/J/f3oH4XFF+tBgfzWQXTNZe4dHrdqjjPOcIg
    rhSHyN+DyZvz2zktdDo=
    &
    
    [+] 返回的密文 (signature): 9d54907ba45d54eaed4bed58cd18de8cb30c7c05
    ======================================
    # 9d54907ba45d54eaed4bed58cd18de8cb30c7c05

    模拟执行时主动调用就可以直接传入这个明文作为字符串参数,并在结束后打印返回值。

    这里Unidbg代码篇幅比较长就不放在文章当中了。有需要的话可以到附件进行下载。总的来说就是补完环境之后,能够通过 JNI 日志看到大部分信息了。

    下面展示一些 JNI 的日志:

    获取包名与JNI字符串转换

    首先就是获取包名,然后将输入的字符串转成字符指针方便后续调用。

    JNI NewStringUTF调用日志

    接着就是 newStringUTF,通过这个可以发现,这里的字符串后面拼接了一串新的东西:

    MOBILE-V1-PRODUCT-7D74899B338B4F348E2383970CC09991E8E8D8F2BC744EF0BEE94D76D718C089

    MessageDigest.update调用日志

    接着就是使用 toUpperCase 将参数转成大写。

    接着就是获取了 SHA-1 哈希算法的实例,接着调用 update,这里传入的参数就是拼接上特定字符串的新参数,最后调用 digest 进行哈希运算。然后 toHexString 之后通过 StringBuilder.append 进行拼接,最后拼接的就是明文字符串。

    StringBuilder.append日志1
    StringBuilder.append日志2
    最终签名结果日志

    最后可以观察到特征串和我们hook到的一模一样。说明这个模拟执行流程正确了。

    其实通过这个 Unidbg 已经几乎把这个 signature 生成方式给看得差不多了,首先就是拼接一串特定字符串,接着转大写,然后进行 SHA-1。从而完成 signature 参数的生成。这个加密函数貌似并没有自己实现加密逻辑,全都是通过 JNI 来调用 java 层的函数以及加密标准库的。

    那么 Unidbg 分析完了,接下来可以试试 IDA 来分析这个 ollvm

    静态分析

    进入大致浏览了一下,好像只有1千多行,试着分析一下。进入函数后,开始先通过TLS读取了一个值,然后就开始获取包名了(分析之后得出)。
    获取包名函数调用

    来看看这个 get_package_name 函数是怎么样的:
    get_package_name函数CFG

    处理ollvm中BCF的方法

    方法一 静态分析

    可以看到这个函数也被 ollvm 混淆了一下,不过代码量不多,这里直接从头分析一下:
    get_package_name函数开头代码

    首先进来就是给 v12 进行赋值,用作下面状态机的一个流程跳转。在 ollvm 中很多这种永真或者永假的条件,此时可以进行化简,实在不会的丢给ai化简。这里可以看到实际上执行的是:$a \times (a-1)$,那么起始就是偶数乘奇数,结果就是偶数,最低位为0, 接着取反,此时最低位为1,或上 0xFFFFFFFE,运算得出 0xFFFFFFFF 那么这个判断条件就为真,所以v12 = 1

    所以这里事实上就只是对 x_149 的最低位做判断。

    继续往下看,并记录状态:

    v12 = 1;
    v3 = 0xB5E7D523;

    方法二 利用IDA的分析引擎

    这里点击 x_149,然后给这个 x_149 一个值(这里我使用的是IDA Python来进行赋值):
    IDA中x_149变量值

    接着将 .bss 段的可写权限(W)关掉,这样就能够利用 IDA 引擎自动将这些虚假控制流去掉了。
    IDA段权限设置

    x_150 处理手法也差不多,效果如下:
    去混淆后的代码片段

    接着回到函数继续分析。另外两个先不管,如果用到了再进行分析。

    接着进入 while(1) 循环:
    while循环状态判断

    由于此时的 v3 = 0xB5E7D523 不满足 whileif 的条件,直接走到了 if-else 这个位置,经过化简之后这里的条件判断其实就是:if(v12 || v13)

    此时的 v12 = 1,所以这里v3被赋值成 0xEA4F3F0B,接着就重新进行 while 循环,此时 v3 满足了 while ( v3 > (int)0xCF812BC6 ),则进入循环,此时会进入到这个 else 分支:
    获取Context与MethodID的代码块

    这里通过调用 JNI 的函数来获取 context,接着获取 pkgname,如果获取成功的话 v15 = 0。这里调用函数用到的字符串都是加密的,这里先分析完整个大体流程之后再讲如何解决这个问题。

    执行完成这个 else 分支之后 v3 = 0xB19E5A69,接着就会因为不满足循环条件而跳出循环。接着就是这里的 if,继续 break
    状态判断与跳出循环

    接着给 v3 继续赋值:
    状态码赋值

    接着到下一个 while 循环:
    下一个while循环逻辑

    先走else块,然后 v3 = -8285439,接着就是 if 块,通过 CallObjectMethod 调用 getPackageName 获取 pkgName 存放到 v16。然后v3被设置成 1958226928,接着往下:
    返回包名前的代码

    给将v16也就是 pkgName 赋值给 v11,然后调整状态码,进入下一轮循环后直接就从 if ( v3 != 1958226928 ) 处返回 pkgName 了。所以这个函数其实原本的逻辑就是获取 pkgName 后返回,去掉混淆之后就是这样的:

    jstring sub_154C(JNIEnv *env, jobject ctx)
    {
        jclass cls = (*env)->FindClass(env, "android/content/ContextWrapper");
        jmethodID mid = (*env)->GetMethodID(env, cls, "getPackageName", "()Ljava/lang/String;");
        (*env)->DeleteLocalRef(env, cls);
        if (mid == NULL) {
            return (*env)->NewStringUTF(env, "");
        }
        return (jstring)(*env)->CallObjectMethod(env, ctx, mid);
    }

    接下来就来解决一下字符串加密的问题

    解决字符串加密的方式

    方法一 分析解密函数

    可以看到做的操作就是获取 JNI 的方法 ID,方便后续调用。可以看到这里的本该传入的字符串好像都是被加密的,点击 xmmword_2C010 进入看看情况:
    加密的字符串数据

    很明显确实是被加密了,那么要怎么还原回原本的字符串呢?这里可以使用交叉引用,发现只有这里是进行了写操作的,直接跳转到这个位置
    字符串数据交叉引用
    字符串解密赋值代码

    接着就是一步步往上跟踪,最后定位到这个位置:
    字符串解密逻辑代码

    这里其实就是一个简单的异或加密,但是数据量太多,这个就交给 AI 来帮忙了, xmmword_2C010 这里的字符串并不只是这个地方的,还延伸到了 0x2C02E 这个位置,遇到了 ALIGN 才算结束:
    加密字符串数据结尾

    按照上面的方法定位解密的位置,最后能够写出下面的脚本:

    datasets = [
        [15, 0, 10, 28, 1, 7, 10, 65, 13, 1, 0, 26, 11, 0, 26, 65,0x2D, 0x01, 0x00, 0x1A, 0x0B, 0x16, 0x1A, 0x39, 0x1C, 0x0F, 0x1E, 0x1E, 0x0B, 0x1C, 0x6E],
    ]
    keys = [0x6E]
    
    for i, (data, key) in enumerate(zip(datasets, keys)):
        out = bytes(b ^ key for b in data)
    
        print(f"[{i}]  hex:", out.hex())
        print("     bytes:", out)

    脚本解密出的字符串

    这里用到的加密字符串基本上都能按照这个方法进行解密。

    方法二 dump文件

    使用上面的方法来获取解密函数效率会非常慢,而且写注释看的不舒服。通过刚才的分析可以发现,这些字符串解密之后都是存放到原来的位置上的,那么我们如果可以直接在这些位置上修改成明文的话,反编译当中也可以看到明文字符串了。

    那么来分析一下这个解密函数在什么时候会被调用,以此来确定 dump 点:
    init_array中的解密函数引用

    通过交叉引用可以看到,是在 init_array 当中被调用的,而这个 so 会在进行了一次登录之后加载。这样就很方便了,直接先登录一次,让so加载,加载完成后字符串解密完成。接着就可以直接 dump 了,连 dump 点都不需要找,这里使用 Frida 进行 dump

    var TARGET_SO = "liblogin_encrypt.so"; 
    var APP_PACKAGE_NAME = "com.ximalaya.ting.android";
    
    function dump_login_enc_lib(moduleName) {
        var module = Process.getModuleByName(moduleName);
        if (module === null) {
            console.log("[-] 找不到模块: " + moduleName);
            return;
        }
    
        console.log("[+] 找到模块: " + module.name);
        console.log("[+] 基址: " + module.base);
        console.log("[+] 大小: " + module.size);
    
        var dumpPath = "/data/data/" + APP_PACKAGE_NAME + "/files/" + module.name + "_dump.so";
    
        try {
            var file = new File(dumpPath, "wb");
            if (file) {
                // 读取整个模块的内存
                Memory.protect(module.base, module.size, "rwx");
                var memoryBuffer = module.base.readByteArray(module.size);
                file.write(memoryBuffer);
                file.flush();
                file.close();
                console.log("
  • Dump 成功!文件已保存至: " + dumpPath);             console.log("
  • 请使用命令将文件 pull 到电脑: adb pull " + dumpPath);         }     } catch (e) {         console.log("[-] Dump 失败: " + e);     } } dump_login_enc_lib(TARGET_SO);
  • dump下来的so使用 soFixed 来修复一下,由于在加载到内存之后, got 表会被污染,这里将对应解密的字符串数据移植到原来的so上,便于分析。移植的过程中要注意, soFixed 修复的so,节的文件偏移和内存地址是一样的,但是原来的so可能有区别,需要注意。

    这里写个python脚本跑一下,再拿来分析:

    def fixSo():
        original_so_path = "liblogin_encrypt.so"         # 原始加密的SO
        dumped_so_path = "liblogin_encrypt_fixed.so"     # Frida Dump出来的明文SO
        output_so_path = "liblogin_encrypt_decrypted.so"    # 最终缝合产物
    
        data_vaddr_in_dump = 0x2c000
        data_offset_in_orig = 0x1c000
        data_size = 0x2d316 - 0x2c000
        print(f"data size : {data_size}")
        print("
  • 开始执行底层指令缝合手术...")     # 读取 Dump 文件中的明文代码     with open(dumped_so_path, "rb") as f_dump:         f_dump.seek(data_vaddr_in_dump)         decrypted_opcodes = f_dump.read(data_size)     # 读取 原 SO 并进行替换     with open(original_so_path, "rb") as f_orig:         orig_data = bytearray(f_orig.read())     # 将明文代码直接覆盖到原 SO 的对应物理位置     orig_data[data_offset_in_orig : data_offset_in_orig + data_size] = decrypted_opcodes     with open(output_so_path, "wb") as f_out:         f_out.write(orig_data)     print(f"[+] 缝合成功!产物已保存至: {output_so_path}") if __name__ == "__main__":     fixSo()
  • 替换为明文字符串后的代码

    这样看起来就非常舒服了。

    ollvm分析

    接下来继续进行 ollvm 的分析。这里一开始一步步分析 ollvm,也是通过上面 get_package_name 的方法进行分析。发现太牢了,然后试着用交叉引用逃课。首先这里会获取包名,正常来说这个包名一般来说就是app的包名。
    包名赋值代码

    接着对这个 pkg_nname 进行交叉引用,并搜索app的包名:
    对pkg_nname的交叉引用搜索结果

    发现了,然后跳转过去:
    包名校验代码

    这里就是校验获取的包名和app包名是否一致,然后设置op状态码。然后继续跟踪,大致三四个状态码切换之后就会来到这个位置:
    特定字符串赋值代码

    可以看到这两个字符串就是我们 Unidbg 模拟的时候发现拼接到参数当中的特定字符串。接下来可以继续分析状态机,这里我就直接交叉引用看调用了。

    首先对 v160 进行状态引用:
    变量v160交叉引用列表

    可以发现除了一堆字符指针的赋值,就是将 v160 赋值给了 v193。那么此时就到这个地方看看有没有什么特别的。
    变量v193引用处代码

    并没有发现什么特别之处,这里再对193进行交叉引用,发现有用到了 memcpy 这种东西,经过简单分析,发现了几个这种结构:
    参数拼接与内存操作代码

    都是开辟空间,然后进行参数拼接。这里猜测就是参数进行拼接的地方。如果需要更加准确的知道到底是调用了哪一个的话,需要跟着状态机分析。最后这里会通过 newStringUTF 将拼接后的参数转成 jstring。接着就是继续对 v175 进行交叉引用,
    JNI调用与字符串处理代码

    接着就是这个位置,转大写,然后调用 MessageDigest.digest,接着继续引用
    SHA-1哈希计算相关JNI调用

    最后就是这个位置,也就是进行 SHA-1 哈希,然后 toHexString

    至此整个 signature 就差不多完成了。到这里整个 Signature 生成应该是比较明确的了,大致过程就是:将登录参数按固定格式拼接后,追加固定字符串 MOBILE-V1-PRODUCT-7D74899B338B4F348E2383970CC09991E8E8D8F2BC744EF0BEE94D76D718C089,转大写,再做 SHA-1 并转十六进制输出。

    参考

    参数分析:新版Ollvm混淆Signature参数解析-Android安全
    https://bbs.kanxue.com/thread-289361.htm

    Unidbg补环境及Trace:《安卓逆向这档事》第二十四课、Unidbg之补完环境我就睡(上)
    https://bbs.kanxue.com/thread-289361.htm

    希望这份详细的逆向工程JNI分析流程,能为你在云栈社区探索Android安全技术带来启发。




    上一篇:EAIDC 2026现场直击:具身智能开源模型真机竞赛,顶尖高校开发者线下对决
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-4-1 07:53 , Processed in 0.532703 second(s), 41 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表