大猿搜题 sign so 加密参数分析|unidbg

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

前言

「样本: https://www.wandoujia.com/apps/6526244
「版本: 11.4.3」

charles

随便一个接口 sign 参数

「这个 app 有 360 加固,需要先脱壳」

dump dex

「使用的 yang 神的 frida-dump https://github.com/lasting-yang/frida_dump

主要如果报了这个错误,需要给 app 文件的读权限

import os
import zipfile
import argparse


def rename_class(path):
    files = os.listdir(path)
    dex_index = 0
    if path.endswith('/'):
        path = path[:-1]
        print(path)
    for i in range(len(files)):
        if files[i].endswith('.dex'):
            old_name = path + '/' + files[i]
            if dex_index == 0:
                new_name = path + '/' + 'classes.dex'
            else:
                new_name = path + '/' + 'classes%d.dex' % dex_index
            dex_index += 1
            if os.path.exists(new_name):
                continue
            os.rename(old_name, new_name)
    print('[*] 重命名完毕')


def extract_META_INF_from_apk(apk_path, target_path):
    r = zipfile.is_zipfile(apk_path)
    if r:
        fz = zipfile.ZipFile(apk_path, 'r')
        for file in fz.namelist():
            if file.startswith('META-INF'):
                fz.extract(file, target_path)
    else:
        print('[-] %s 不是一个APK文件' % apk_path)


def zip_dir(dirname, zipfilename):
    filelist = []
    if os.path.isfile(dirname):
        if dirname.endswith('.dex'):
            filelist.append(dirname)
    else:
        for root, dirs, files in os.walk(dirname):
            for dir in dirs:
                # if dir == 'META-INF':
                # print('dir:', os.path.join(root, dir))
                filelist.append(os.path.join(root, dir))
            for name in files:
                # print('file:', os.path.join(root, name))

                filelist.append(os.path.join(root, name))

    z = zipfile.ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED)
    for tar in filelist:
        arcname = tar[len(dirname):]

        if ('META-INF' in arcname or arcname.endswith('.dex')) and '.DS_Store' not in arcname:
            # print(tar + " -->rar: " + arcname)
            z.write(tar, arcname)
    print('[*] APK打包成功,你可以拖入APK进行分析啦!')
    z.close()


if __name__ == '__main__':
    args = {
        'dex_path': '/Users/admin/Desktop/project/user/user-frida-hook/hook/frida_dump_dex/unpack/xiaoyuansouti',
        'apk_path': '/Volumes/T7/android/android-file/xiaoyuansouti-11.4.3.apk',
        'output': '/Volumes/T7/android/android-file/xiaoyuansouti-new-11.4.3.apk'
    }

    rename_class(args['dex_path'])
    extract_META_INF_from_apk(args['apk_path'], args['dex_path'])
    zip_dir(args['dex_path'], args['output'])

脱完壳之后再使用以上脚本,合并 dex 生成新的 apk 文件,在反编译

java 层分析

全局搜索 sign 关键词,定位到这个函数 com.fenbi.android.leo.imgsearch.sdk.network.intercepto.SignInterceptor.intercept

继续往下分析,来到了这里 com.fenbi.android.solar.util.bn

在继续就来到了 native 函数处,会调用这个三个函数

frida hook

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


function printLog() {
    var result = '';

    for (var i = 0; i < arguments.length; i++) {
        result += arguments[i] + ' ';
    }

    console.log(getProcessId(), result)
}


function hook() {
    var eClass = Java.use('com.fenbi.android.solar.util.e');

    eClass.zcvsd1wr2t.implementation = function (a, b, c, d) {
        printLog('eClass.zcvsd1wr2t.a: ', a);
        printLog('eClass.zcvsd1wr2t.c: ', c);
        printLog('eClass.zcvsd1wr2t.c: ', c);
        printLog('eClass.zcvsd1wr2t.d: ', d);

        var res = this.zcvsd1wr2t(a, b, c, d);
        printLog('eClass.zcvsd1wr2t.res: ', res);

        return res;
    }

    eClass.awi6d8sdfe.implementation = function (a, b, c, d) {
        printLog('eClass.awi6d8sdfe.a: ', a);
        printLog('eClass.awi6d8sdfe.c: ', c);
        printLog('eClass.awi6d8sdfe.c: ', c);
        printLog('eClass.awi6d8sdfe.d: ', d);

        var res = this.awi6d8sdfe(a, b, c, d);
        printLog('eClass.awi6d8sdfe.res: ', res);

        return res;
    }

    eClass.fxc3fs3red.implementation = function (a, b, c, d) {
        printLog('eClass.fxc3fs3red.a: ', a);
        printLog('eClass.fxc3fs3red.c: ', c);
        printLog('eClass.fxc3fs3red.c: ', c);
        printLog('eClass.fxc3fs3red.d: ', d);

        var res = this.fxc3fs3red(a, b, c, d);
        printLog('eClass.fxc3fs3red.res: ', res);

        return res;
    }
}


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

执行脚本,结果保存到文件里,函数都 hook 成功

unidbg

「老规矩,先搭建架子」

package com.xiayu.xiaoyuansouti;

import com.github.unidbg.AndroidEmulator;
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.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

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

public class GetSign_v1143Test extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public DvmClass EClass;
    public String apkPath = "/Volumes/T7/android/android-file/xiaoyuansouti-11.4.3.apk";

    GetSign_v1143Test() {
        emulator = AndroidEmulatorBuilder.for32Bit().build();
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File(apkPath));
        vm.setVerbose(true);
        DalvikModule dm = vm.loadLibrary("RequestEncoder", true);

        vm.setJni(this);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);

        EClass = vm.resolveClass("com/fenbi/android/solar/util/e");
    }

    public static void main(String[] args) {
        GetSign_v1143Test getSign_v1143Test = new GetSign_v1143Test();
        
        getSign_v1143Test.destroy();
    }

    private void destroy() {
        try {
            emulator.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

跑起来,成功报错,libandroid.so 咱们加一下

加上在跑起来,这次报了环境错误,补上就 OK 了

public void call_zcvsd1wr2t() {
    String methodId = "zcvsd1wr2t(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;";
    EClass.callStaticJniMethodObject(
            emulator, methodId,
            new StringObject(vm, "/ape-news/android/news"),
            new StringObject(vm, "0"),
            0,
            new StringObject(vm, "")
    );
}

直接来 call 目标函数

「中间省略一部分补环境操作。。。。。。」

这个错误,要求返回 Signature array,这是获取 app 签名,需要返回 array 注意不要直接 vm.resolveClass("[android/content/pm/Signature") 可以使用 ArrayObject 构建返回,这样可以避免很多问题

CertificateMeta[] certificateMetas = vm.getSignatures();

DvmObject<?>[] dvmObjects = new DvmObject[certificateMetas.length];

for (int i = 0; i < certificateMetas.length; i++) {
    dvmObjects[i] = vm.resolveClass("android/content/pm/Signature").newObject(certificateMetas[i]);
}

return new ArrayObject(dvmObjects);

具体代码如上,补上继续跑起来

这个是调用 Signature.toChars 函数,返回 char[] 数据

CertificateMeta certificateMeta = (CertificateMeta) dvmObject.getValue();

byte[] bytes = certificateMeta.getData();

DvmObject<?>[] dvmObjects = new DvmObject[bytes.length];

for (int i = 0; i < bytes.length; i++) {
    dvmObjects[i] = vm.resolveClass("C").newObject((char) bytes[i]);
}

return new ArrayObject(dvmObjects);

补完,在跑起来,发现又报错了,不知道啥错误,点进 handler 里看看

是个未实现的 jni 函数,那咱们就去实现一下,参照 GetxxxxArrayElements 代码逻辑,写一个

比如这个 GetIntArrayElements 函数逻辑,通过 getObject 获取对象,在调用 _GetArrayCritical 函数,但是 char[] 没有实现对应的 CharArray,有两种方法

  • 1、自己根据 GetxxxxArrayElements 逻辑写一个

  • 2、实现 CharArray 跟其他的 xxxArray 逻辑保持一致

推荐方法 2,这样写后面可以避免很多问题(龙哥的精讲课里也提到过,基本数据的构建最好使用 unidbg 提供的 class)

新建个 CharArray 文件,代码直接复制其他的 array 改改数据类型就 OK 了

刚刚的环境,咱们使用 CharArray 来构建

jni 这里也是使用,同样的逻辑返回,继续跑起来,还会报个错误

这里在 write 的时候,循环调用 setChar 函数即可

最后在补个环境,也是成功运行出了结果

上一页
下一页