android app so 加密算法分析破解|mtgsig unidbg

android 逆向 27 / 37

本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢!

推荐阅读

本文样本来自 龙哥 csdn 参考链接: csdn 链接
更多 Unidbg 使用和算法还原的教程可见星球: 点击加入龙哥星球

前言

某团 android app mtgsig 参数分析,美食列表接口,版本 11.9.405

charles 抓包

就是它,本文主要分析 mtgsig 参数

java 层分析

反编译 apk 全局搜索 mtgsig,有个赋值点过去,查找用例

这里有 hashMap.put 点过去

这里先是调用了 getBuilder 函数,猜测是获取 url 参数等,在调用 makeHeader

makeHeader 函数,前面是 url 处理,字符串拼接,调用 WTSign.makeHeader 获取结果

此函数又调用了 NBridge.main 函数

NBridge.main 是个 native 函数

libmtguard.so 文件查找

在上述的 NBridge.java 文件里,并没有看到有,so 文件的加载,那 native 函数是在哪个 so

  • 1、可以使用 frida 等工具来分析
  • 2、可以参考别人文章的逻辑来分析
  • 3、可以自己根据经验来分析

根据经验查找也有多种方式,这里就假设你已经阅读了龙哥 csdn 的文章,那就肯定知道 main 函数同时也是初始化函数,既然是初始化哪加载 so 的逻辑,估计也在一起,没错就直接查找 main 函数的交叉引用

有多个但 111 是初始化直接点过去,该逻辑是在 initNative 函数里,查看交叉引用,哪里调用的

在这里调用,如果细心点就会发现,上面有个 if,根据名称可以猜测出来跟 so 相关,查看交叉引用

loadSo 函数里,有个 System.loadLibrary 逻辑,加载成功就 isLoaded = true;

MTGConfigs.MTG 的值正是 mtguard

frida hook

function getProcessId() {
    var androidProcess = Java.use('android.os.Process');
    return 'processId: ' + androidProcess.myTid() + ' - ';
}

function hook() {
    var javaString = Java.use('java.lang.String');
    var NBridge = Java.use('com.meituan.android.common.mtguard.NBridge');
    var WTSign = Java.use('com.meituan.android.common.mtguard.wtscore.plugin.sign.core.WTSign');

    WTSign.makeHeader.implementation = function (a, b) {
        console.log(getProcessId(), 'WTSign.makeHeader.a: ', a);
        console.log(getProcessId(), 'WTSign.makeHeader.b: ', javaString.$new(a));

        var res = this.makeHeader(a, b);
        console.log(getProcessId(), 'WTSign.makeHeader.res: ', res);

        return res;
    }

    NBridge.main.implementation = function (a, b) {
        console.log(getProcessId(), 'NBridge.main.a: ', a);
        console.log(getProcessId(), 'NBridge.main.b: ', b);

        if (b.length === 3) {
            console.log(getProcessId(), 'NBridge.main.b.0: ', b[0]);
            console.log(getProcessId(), 'NBridge.main.b.1: ', b[1]);
            console.log(getProcessId(), 'NBridge.main.b.2: ', b[2]);
        } else {
            for (var i = 0; i < b.length; i++) {
                console.log(getProcessId(), 'NBridge.main.b.i: ', i, ' data: ', b[i]);
            }
        }

        var res = this.main(a, b);

        console.log(getProcessId(), 'NBridge.main.res: ', res);

        for (var j = 0; j < res.length; j++) {
            console.log(getProcessId(), 'NBridge.main.res.j: ', j, ' data: ', res[j]);
        }

        return res;
    }
}

function main() {
    Java.perform(function () {
        hook();
    })
}

setImmediate(main);

// frida -UF -l hook.js
// frida -UF -l hook.js | tee hook.log

使用 frida hook 一下,看看 main 函数的输入输出是啥

main 函数有两个参数

  • 1、int 203
  • 2、object array,1、固定的 key,2、url相关参数,3、int 数字

下面使用 unidbg 运行 main 函数

unidbg

package com.xiayu.meituan;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
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.memory.Memory;

import java.io.File;
import java.io.IOException;

public class MtGsigTest extends AbstractJni {
    private final AndroidEmulator emulator;
    private final Module module;
    private final VM vm;
    private final DvmClass NBridge;

    public String apkPath = "apkPath";
    public String soPath = "soPath";

    private static LibraryResolver createLibraryResolver() {
        return new AndroidResolver(23);
    }

    private static AndroidEmulator createARMEmulator() {
        return AndroidEmulatorBuilder
                .for32Bit()
                .build();
    }

    MtGsigTest() {
        emulator = createARMEmulator();
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(createLibraryResolver());
        vm = emulator.createDalvikVM(new File(apkPath));
        vm.setVerbose(true);

        DalvikModule dm = vm.loadLibrary(new File(soPath), true);
        vm.setJni(this);

        dm.callJNI_OnLoad(emulator);
        module = dm.getModule();

        NBridge = vm.resolveClass("com/meituan/android/common/mtguard/NBridge");
    }

    public static void main(String[] args) throws IOException {
        MtGsigTest mtGsig = new MtGsigTest();
        mtGsig.destroy();
    }

    private void destroy() throws IOException {
        emulator.close();
    }
}

先把架子搭好,跑起来,没啥问题

call main 111

public void callMain111() {
    DvmObject<?> strRc = NBridge.callStaticJniMethodObject(
        emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;",
        111,
        new ArrayObject(vm.resolveClass("java/lang/object").newObject(null))
    );

    System.out.println("callMain111: " + strRc.getValue());
}

有了前面的分析经验,得知,需要先调用 main 初始化加载函数,跑起来

这里报了个错误,正常补环境即可,具体的值,可以使用 frida call 或者 jnitrace 获取

  • 写文章之前博主已经补过一遍了,所以这里就直接贴上 frida hook 的代码
function call() {
    var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
    var context = currentApplication.getApplicationContext();

    var PackageManager = context.getPackageManager();
    var getPackageInfo = PackageManager.getPackageInfo('com.sankuai.meituan', 64);

    var NBridge = Java.use('com.meituan.android.common.mtguard.NBridge');

    console.log('NBridge.getDfpId             : ', NBridge.getDfpId());
    console.log('NBridge.getPicName           : ', NBridge.getPicName());
    console.log('NBridge.getSecName           : ', NBridge.getSecName());
    console.log('NBridge.getMtgVN             : ', NBridge.getMtgVN());

    console.log('PackageManager.GET_SIGNATURES: ', PackageManager.GET_SIGNATURES.value);
    console.log('PackageInfo.versionCode      : ', getPackageInfo.versionCode.value);

    console.log('context.getPackageCodePath   : ', context.getPackageCodePath());
}

中间省略一部分补环境逻辑。。。。。。

这个直接返回 apk 的路径即可,后面在补一些环境,即可成功,下面直接调用 main 203

call main 203

public void callMain203() {
    DvmObject<?> strRc = NBridge.callStaticJniMethodObject(
        emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;",
        203,
        new ArrayObject(
            new StringObject(vm, "key"),
            new ByteArray(vm, "url 参数".getBytes()),
            DvmInteger.valueOf(vm, 2)
        )
    );

    System.out.println("callMain203: " + ((DvmObject<?>[]) ((ArrayObject) strRc).getValue())[0]);
}

代码跑起来发现没问题,但是结果也没出来,肯定是有问题,查看 log 发现有读取文件,读的还是 apk 哪咱们就补一下这个文件

  • 1

  • 2

  • 3
@Override
public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
    System.out.println("resolve.pathname-11111: " + pathname);

    if (("/data/app/com.sankuai.meituan-TEfTAIBttUmUzuVbwRK1DQ==/base.apk").equals(pathname)) {
        return FileResult.<AndroidFileIO>success(new SimpleFileIO(oflags, new File(apkPath), pathname));
    }
    return null;
}

补完再次跑起来

这里又报了个错误,前面 frida call 出来了,直接加上

再次运行,就没啥问题了,结果也正常出来了

7 条评论
本文作者:
本文链接: https://www.qinless.com/?p=1033
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 qinless 的博客!
100

7 条评论

浩然

新版本使用unidbg 生成的a9 比app生成的短,有人遇到过这个问题吗

回复

tofu

现在的sign 到a10了。请问你这文章的结果有a10吗。我hook出来只有到a6。用不了

回复

会爬山的小脑虎

@tofu 那应该是版本的问题了,我这个是老版本的

回复

tofu

@会爬山的小脑虎 可以知道一下你用的什么版本吗。下了好几个版本都没发现只有6位数的

会爬山的小脑虎

@tofu 具体我也忘记了,我记得好像是 10.x 的

于与禹

大佬,在步环境的时候,这种应该是android本身的一些东西吧,这个需要怎么补呀,Landroid/content/pm/PackageManager;->signatures:[Landroid/content/pm/Signature;,应该是一个数组

回复

会爬山的小脑虎

@于与禹 对是的,返回这个环境的正确数据就可以了,

回复

发表评论

返回顶部