吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 805|回复: 22
上一主题 下一主题
收起左侧

[Android 原创] 某房地产APP请求逆向分析

[复制链接]
跳转到指定楼层
楼主
jie1525050 发表于 2026-7-8 18:50 回帖奖励

升本考试结束后,距离 9 月开学还有一段时间,最近主要在家学习和实践 Web 自动化以及 Android 逆向。刚好这段时间把该 APP 的相关加密参数分析出来了,所以借此记录一下本次逆向过程中用到的思路和方法。整体来看,这个 APP 的加密难度不算高,native 层主要实现的是标准的 HMAC-SHA256 算法,代码层面只做了少量函数名混淆,没有 OLLVM 这类强混淆保护,因此非常适合作为 Android 逆向入门练习样本。  

一.样本

1.加固

2.app类型

发现确实有flutter的标识,但是后续查看的时候走的还是java

3.逆向参数

加密参数是WLL-KGSA

二.逆向思路

1.定位Java层函数

搜索WLL-KGSA后发现是一个常量,查询它的交叉引用后发现有3个位置都用到了最后发现3个位置都在同一个代码块中

通过查看代码可以看出,这里会先判断签名结果是否满足条件。如果 cVarO50.o50() 返回 false,就只移除原有的 Authorization 请求头;如果返回 true,则会先移除旧请求头,再把 cVarO50.p50() 生成的签名字符串重新添加到 Authorization 请求头中。cVarO50又是由d.o50(this.credential, aVarR50);生成的

a aVarR50 = new a(request).q50(hashSet).p50(this.nounce).r50(this.timestamp);
if (this.credential == null && (bVar = SecManager.mParams) != null) {
    this.credential = bVar.o50();
}
c cVarO50 = d.o50(this.credential, aVarR50);
if (!cVarO50.o50()) {
    cVarO50 = d.o50(this.credential, aVarR50);
}
if (!cVarO50.o50()) {
    requestBuild = request.newBuilder().removeHeader(AUTHORIZATION_HEADER).build();
} else {
    requestBuild = request.newBuilder().removeHeader(AUTHORIZATION_HEADER).addHeader(AUTHORIZATION_HEADER, cVarO50.p50()).build();
}
return chain.proceed(requestBuild);

首先来到cVarO50.p50(),这里是最终签名字符串的拼接位置,并不是签名真正生成的位置。它只是从 this.sign 中取出已经生成好的字段,然后按照固定格式拼接成请求头内容,所以这个位置已经偏晚,需要继续往上追。  

public String p50() {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[0], this, changeQuickRedirect, false, 75785, new Class[0], String.class);
        if (patchProxyResultProxy.isSupported) {
            return (String) patchProxyResultProxy.result;
        }
        if (!this.satisfy || this.sign == null) {
            return "";
        }
        ArrayList arrayList = new ArrayList(10);
        arrayList.add("accessKeyId=" + this.sign.getAccessKeyId());
        arrayList.add("nonce=" + this.sign.getNonce());
        arrayList.add("timestamp=" + this.sign.I5());
        if (this.sign.g4() != null && this.sign.g4().length > 0) {
            arrayList.add("signedHeaders=" + StringUtils.join(this.sign.g4(), ","));
        }
        if (StringUtils.isNotEmpty(this.sign.n1())) {
            arrayList.add("principal=" + this.sign.n1());
        }
        arrayList.add("signature=" + this.sign.getSignature());
        return this.sign.getAlgorithm() + " " + StringUtils.join(arrayList.iterator(), "; ");
    }

来到d.o50这里看看,这个就是判断了两个参数有没有,最后返回的是new host.ar0.c,构造参数是fVar.build().tK(aVar)但是进去看了发现两个方法都是接口,所以先去看看new host.ar0.c里面

public final class d {
    public static ChangeQuickRedirect changeQuickRedirect;

    public static host.ar0.c o50(a aVar, f fVar) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{aVar, fVar}, null, changeQuickRedirect, true, 75751, new Class[]{a.class, f.class}, host.ar0.c.class);
        if (patchProxyResultProxy.isSupported) {
            return (host.ar0.c) patchProxyResultProxy.result;
        }
        if (fVar == null) {
            return new host.ar0.c(false, "Invalid signRequestBuilder arguments. ");
        }
        if (aVar == null) {
            return new host.ar0.c(false, "Invalid credential arguments. ");
        }
        try {
            return new host.ar0.c(fVar.build().tK(aVar));
        } catch (ApiSignatureException e) {
            KeLog.z50(e);
            return new host.ar0.c(e);
        }
    }
}

我们走的是public c(e eVar)函数,他调用的另一个构造函数public c(boolean z, e eVar, String str)这里面可以看见this.sign就等于传进去的e eVar,并且看到了前面的判断cVarO50.o50()这个函数返回的是this.satisfy是 参数一,也就是 true。所以前面的判断没有问题走的判断是else,现在就是要去看fVar.build().tK(aVar)这里肯定就是加密生成的地方了,这个是一个链式调用所以首先去看fVar.build(),fVar又是o50的第二个参数所以去看看new a(request).q50(hashSet).p50(this.nounce).r50(this.timestamp);

public final class c {
    public static ChangeQuickRedirect changeQuickRedirect;
    private String code;
    private final String reason;
    private final boolean satisfy;
    private e sign;

    public boolean o50() {
        return this.satisfy;
    }

    public c(e eVar) {
        this(true, eVar, null);
    }

    public c(boolean z, String str) {
        this.code = "Undefined";
        this.satisfy = z;
        this.reason = str;
    }

    public c(boolean z, e eVar, String str) {
        this.code = "Undefined";
        this.satisfy = z;
        this.sign = eVar;
        this.reason = str;
    }

    public c(ApiSignatureException ApiSignatureException) {
        this.code = "Undefined";
        this.satisfy = false;
        this.code = ApiSignatureException.getCode();
        this.reason = ApiSignatureException.getMessage();
    }

    public String p50() {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[0], this, changeQuickRedirect, false, 75785, new Class[0], String.class);
        if (patchProxyResultProxy.isSupported) {
            return (String) patchProxyResultProxy.result;
        }
        if (!this.satisfy || this.sign == null) {
            return "";
        }
        ArrayList arrayList = new ArrayList(10);
        arrayList.add("accessKeyId=" + this.sign.getAccessKeyId());
        arrayList.add("nonce=" + this.sign.getNonce());
        arrayList.add("timestamp=" + this.sign.I5());
        if (this.sign.g4() != null && this.sign.g4().length > 0) {
            arrayList.add("signedHeaders=" + StringUtils.join(this.sign.g4(), ","));
        }
        if (StringUtils.isNotEmpty(this.sign.n1())) {
            arrayList.add("principal=" + this.sign.n1());
        }
        arrayList.add("signature=" + this.sign.getSignature());
        return this.sign.getAlgorithm() + " " + StringUtils.join(arrayList.iterator(), "; ");
    }
}

首先走的是public a(Request request)this.request成员变量赋值request,接着走的是public a q50(Set<String> set),传入的是一个字符串集合给了this.signedHeaders,再然后走的是public a p50(String str)传入的是字符串给到了this.nonce,最后走的是public a r50(Long l)传入的是一个long类型给到了this.timestamp很明显是时间戳 。所以这里new的都是给成员变量赋值,猜测是后面加密的时候会用到。

public class a implements f {
    public static ChangeQuickRedirect changeQuickRedirect;
    private String nonce;
    private final Request request;
    private Set<String> signedHeaders = new HashSet(10);
    private Long timestamp;

    public a(Request request) {
        this.request = request;
    }

    public a q50(Set<String> set) {
        this.signedHeaders = set;
        return this;
    }

    public a p50(String str) {
        this.nonce = str;
        return this;
    }

    public a r50(Long l) {
        this.timestamp = l;
        return this;
    }

    @Override // host.wq0.f
    public e build() throws InvalidParameterException {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[0], this, changeQuickRedirect, false, 75756, new Class[0], e.class);
        if (patchProxyResultProxy.isSupported) {
            return (e) patchProxyResultProxy.result;
        }
        host.zq0.a aVar = new host.zq0.a();
        aVar.o50(ConstantUtil.METHOD, this.request.method().toUpperCase());
        aVar.o50("path", this.request.url().encodedPath());
        aVar.o50("host", o50(this.request));
        String strEncodedQuery = this.request.url().encodedQuery();
        if (StringUtils.isNotEmpty(strEncodedQuery)) {
            aVar.u50(strEncodedQuery);
        }
        if (StringUtils.isNotEmpty(this.nonce)) {
            aVar.setNonce(this.nonce);
        }
        Long l = this.timestamp;
        if (l != null) {
            aVar.s50(l);
        }
        String strHeader = this.request.header("Content-MD5");
        if (StringUtils.isNotEmpty(strHeader)) {
            aVar.o50("content-md5", strHeader);
        }
        Set<String> set = this.signedHeaders;
        if (set != null && !set.isEmpty()) {
            aVar.r50(StringUtils.join(this.signedHeaders.iterator(), ","));
        }
        if (aVar.g4() != null && aVar.g4().length > 0) {
            for (String str : aVar.g4()) {
                if (StringUtils.isNotEmpty(str)) {
                    String strReplace = str.toLowerCase().replace("_", "-");
                    String strHeader2 = this.request.header(str);
                    if (TextUtils.isEmpty(strHeader2) && "User-Agent".equals(str)) {
                        strHeader2 = Version.userAgent();
                    }
                    aVar.o50(strReplace, strHeader2);
                }
            }
        }
        return aVar;
    }

    public String o50(Request request) {
        String str;
        boolean z = true;
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{request}, this, changeQuickRedirect, false, 75757, new Class[]{Request.class}, String.class);
        if (patchProxyResultProxy.isSupported) {
            return (String) patchProxyResultProxy.result;
        }
        String strHeader = request.header("Host");
        if (StringUtils.isNotEmpty(strHeader)) {
            return strHeader;
        }
        if (!request.isHttps() && request.url().port() == 80) {
            z = false;
        }
        boolean z2 = (request.isHttps() && request.url().port() == 443) ? false : z;
        StringBuilder sb = new StringBuilder();
        sb.append(request.url().host());
        if (z2) {
            str = Constants.COLON_SEPARATOR + request.url().port();
        } else {
            str = "";
        }
        sb.append(str);
        return sb.toString();
    }
}

接着来看public e build() throws InvalidParameterException,是一个重写的函数,是继承了接口f,中间是提取了request的参数做了一些判断,最后返回的是aVar是由new host.zq0.a();生成的

@Override // host.wq0.f
    public e build() throws InvalidParameterException {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[0], this, changeQuickRedirect, false, 75756, new Class[0], e.class);
        if (patchProxyResultProxy.isSupported) {
            return (e) patchProxyResultProxy.result;
        }
        host.zq0.a aVar = new host.zq0.a();
        aVar.o50(ConstantUtil.METHOD, this.request.method().toUpperCase());
        aVar.o50("path", this.request.url().encodedPath());
        aVar.o50("host", o50(this.request));
        String strEncodedQuery = this.request.url().encodedQuery();
        if (StringUtils.isNotEmpty(strEncodedQuery)) {
            aVar.u50(strEncodedQuery);
        }
        if (StringUtils.isNotEmpty(this.nonce)) {
            aVar.setNonce(this.nonce);
        }
        Long l = this.timestamp;
        if (l != null) {
            aVar.s50(l);
        }
        String strHeader = this.request.header("Content-MD5");
        if (StringUtils.isNotEmpty(strHeader)) {
            aVar.o50("content-md5", strHeader);
        }
        Set<String> set = this.signedHeaders;
        if (set != null && !set.isEmpty()) {
            aVar.r50(StringUtils.join(this.signedHeaders.iterator(), ","));
        }
        if (aVar.g4() != null && aVar.g4().length > 0) {
            for (String str : aVar.g4()) {
                if (StringUtils.isNotEmpty(str)) {
                    String strReplace = str.toLowerCase().replace("_", "-");
                    String strHeader2 = this.request.header(str);
                    if (TextUtils.isEmpty(strHeader2) && "User-Agent".equals(str)) {
                        strHeader2 = Version.userAgent();
                    }
                    aVar.o50(strReplace, strHeader2);
                }
            }
        }
        return aVar;
    }

进去看了发现没有tK函数,因为他是继承的g类,所以去g类看看

public class a extends g {
    public static ChangeQuickRedirect changeQuickRedirect;

    public a u50(String str) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{str}, this, changeQuickRedirect, false, 75768, new Class[]{String.class}, a.class);
        if (patchProxyResultProxy.isSupported) {
            return (a) patchProxyResultProxy.result;
        }
        String[] strArrSplit = str.split("&");
        ArrayList arrayList = new ArrayList(strArrSplit.length);
        for (String str2 : strArrSplit) {
            if (StringUtils.isNotEmpty(str2) && !str2.startsWith("X-Auth-Signature=")) {
                arrayList.add(str2);
            }
        }
        Collections.sort(arrayList);
        o50("query", StringUtils.join(arrayList.iterator(), "&"));
        return this;
    }
}

先看public e tK(a aVar)方法,最后返回的是this,但是p50(aVar.getAccessKeyId()).q50(c.o50("LJAPPVA").kB(t50(), aVar.getAccessKeyId(), this.timestamp));很明显他就是去获取参数最后去调用c.o50("LJAPPVA").kB函数,因为p50q50函数都是在这个类里面并且做的都是赋值返回的也都是this.

public class g implements e {
    public static ChangeQuickRedirect changeQuickRedirect;
    private String accessKeyId;
    private String nonce;
    private String principal;
    private String signature;
    private String[] signedHeaders;
    private Long timestamp;
    private List<String> parameters = new ArrayList();
    private String[] keyLogs = null;

    @Override // host.wq0.e
    public String getAlgorithm() {
        return "LJAPPVA";
    }

    public g o50(String str, String str2) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{str, str2}, this, changeQuickRedirect, false, 75752, new Class[]{String.class, String.class}, g.class);
        if (patchProxyResultProxy.isSupported) {
            return (g) patchProxyResultProxy.result;
        }
        if (StringUtils.isEmpty(str)) {
            throw new InvalidParameterException("Sign parameter key can not be null or empty.");
        }
        if (StringUtils.isNotEmpty(str2)) {
            this.parameters.add(str + "=" + str2);
        }
        return this;
    }

    public g r50(String str) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{str}, this, changeQuickRedirect, false, 75753, new Class[]{String.class}, g.class);
        if (patchProxyResultProxy.isSupported) {
            return (g) patchProxyResultProxy.result;
        }
        this.signedHeaders = str.split(",");
        return this;
    }

    public g q50(String[] strArr) {
        this.keyLogs = strArr;
        if (strArr != null && strArr.length > 0) {
            this.signature = strArr[0];
        }
        return this;
    }

    public g p50(String str) {
        this.accessKeyId = str;
        return this;
    }

    public void setNonce(String str) {
        this.nonce = str;
    }

    public void s50(Long l) {
        this.timestamp = l;
    }

    @Override // host.wq0.e
    public String getAccessKeyId() {
        return this.accessKeyId;
    }

    @Override // host.wq0.e
    public String getNonce() {
        return this.nonce;
    }

    @Override // host.wq0.e
    public Long I5() {
        return this.timestamp;
    }

    @Override // host.wq0.e
    public String[] g4() {
        return this.signedHeaders;
    }

    @Override // host.wq0.e
    public String n1() {
        return this.principal;
    }

    @Override // host.wq0.e
    public String getSignature() {
        return this.signature;
    }

    public String t50() {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[0], this, changeQuickRedirect, false, 75754, new Class[0], String.class);
        if (patchProxyResultProxy.isSupported) {
            return (String) patchProxyResultProxy.result;
        }
        if (StringUtils.isEmpty("LJAPPVA")) {
            throw new InvalidParameterException("Algorithm can not be null or empty.");
        }
        if (StringUtils.isEmpty(this.accessKeyId)) {
            throw new InvalidParameterException("AccessKeyId can not be null or empty.");
        }
        if (StringUtils.isEmpty(this.nonce)) {
            this.nonce = RandomStringUtils.randomAlphanumeric(32);
        }
        if (this.timestamp == null) {
            this.timestamp = Long.valueOf(new Date().getTime() / 1000);
        }
        ArrayList arrayList = new ArrayList(this.parameters);
        arrayList.add("accessKeyId=" + this.accessKeyId);
        arrayList.add("nonce=" + this.nonce);
        arrayList.add("timestamp=" + this.timestamp);
        String[] strArr = this.signedHeaders;
        if (strArr != null && strArr.length > 0) {
            arrayList.add("signedHeaders=" + StringUtils.join(this.signedHeaders, ","));
        }
        if (StringUtils.isNotEmpty(this.principal)) {
            arrayList.add("principal=" + this.principal);
        }
        Collections.sort(arrayList);
        return StringUtils.join(arrayList.iterator(), "&");
    }

    @Override // host.wq0.e
    public e tK(a aVar) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{aVar}, this, changeQuickRedirect, false, 75755, new Class[]{a.class}, e.class);
        if (patchProxyResultProxy.isSupported) {
            return (e) patchProxyResultProxy.result;
        }
        p50(aVar.getAccessKeyId()).q50(c.o50("LJAPPVA").kB(t50(), aVar.getAccessKeyId(), this.timestamp));
        return this;
    }
}

首先去o50看看,这个函数的作用就是根据传入的算法名,从 signerRegistry 里找到对应的签名器类,并通过反射创建一个签名器对象返回.所以他返回的是一个对象,接下来的重点就是kB函数了

    public static b o50(String str) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{str}, null, changeQuickRedirect, true, 75750, new Class[]{String.class}, b.class);
        if (patchProxyResultProxy.isSupported) {
            return (b) patchProxyResultProxy.result;
        }
        if (StringUtils.isEmpty(str)) {
            throw new InvalidParameterException("Algorithm should be provided.");
        }
        Class<? extends b> cls = signerRegistry.get(str);
        if (cls == null) {
            throw new InvalidParameterException("Invalid algorithm " + str);
        }
        try {
            return cls.getConstructor(new Class[0]).newInstance(new Object[0]);
        } catch (Exception e) {
            KeLog.z50(e);
            throw new UnSupportedException("Create signer instance failed.", e);
        }
    }

是一个接口,直接搜索大法

public interface b {
    String[] kB(String str, String str2, Long l);
}

很明显就是第二个

确实是在这里最后返回的是strArrSign,是由SecManager.sign(str, str2, l.toString());生成的

public String[] kB(String str, String str2, Long l) {
        PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{str, str2, l}, this, changeQuickRedirect, false, 75760, new Class[]{String.class, String.class, Long.class}, String[].class);
        if (patchProxyResultProxy.isSupported) {
            return (String[]) patchProxyResultProxy.result;
        }
        if (StringUtils.isEmpty(str)) {
            throw new InvalidParameterException("String to sign can not be null or empty.");
        }
        try {
            String[] strArrSign = SecManager.sign(str, str2, l.toString());
            if (strArrSign != null && strArrSign.length > 0) {
                String lowerCase = strArrSign[0].toLowerCase();
                try {
                    if (strArrSign.length > 6) {
                        strArrSign[6] = strArrSign[6] + "++" + lowerCase;
                    }
                } catch (Throwable th) {
                    KeLog.z50(th);
                }
                byte[] bArrO50 = o50(lowerCase);
                try {
                    if (strArrSign.length > 6) {
                        StringBuilder sb = new StringBuilder();
                        for (byte b : bArrO50) {
                            sb.append((int) b);
                        }
                        strArrSign[6] = strArrSign[6] + "++" + sb.toString();
                    }
                } catch (Throwable th2) {
                    KeLog.z50(th2);
                }
                strArrSign[0] = Base64.encodeToString(bArrO50, 2);
                try {
                    if (strArrSign.length > 4) {
                        strArrSign[4] = strArrSign[4] + "++" + strArrSign[0];
                    }
                } catch (Throwable th3) {
                    KeLog.z50(th3);
                }
            }
            return strArrSign;
        } catch (Throwable th4) {
            KeLog.z50(th4);
            throw new UnSupportedException(th4.getMessage());
        }
    }

不出所料是一个native函数,至此java层就已经找完了,接下来就是so层逆向了.

public static native String[] sign(String str, String str2, String str3);

2.unidbg

2.1 搭架子

因为样本只包含 arm64-v8a,所以 unidbg 需要使用 64 位模拟器  

package com.project.beike;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.arm.backend.Unicorn2Factory;
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 java.io.File;

public class BeiKe3 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final DvmClass NativeApi;
    private final VM vm;

    public BeiKe3() {
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new Unicorn2Factory(true))
                .setProcessName("com.lianjia.beike")
                .build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/project/beike/BeiKev3.04.31.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        DalvikModule dm = vm.loadLibrary("kmsec", true);
        NativeApi = vm.resolveClass("com.ke.securitylib.SecManager");
        dm.callJNI_OnLoad(emulator);
    }

    public static void main(String[] args) {
        BeiKe3 beiKe = new BeiKe3();

    }
}

2.2 第一次初始化运行

com/ke/securitylib/SecManager->mContext:Landroid/content/Context;

运行后报错,很明显他获取的是一个类的静态成员变量,我们先去用frida打印看看  

java.lang.UnsupportedOperationException: com/ke/securitylib/SecManager->mContext:Landroid/content/Context;
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
at com.project.beike.BeiKe3.getStaticObjectField(BeiKe3.java:45)
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)
at com.github.unidbg.linux.android.dvm.DalvikVM64$142.handle(DalvikVM64.java:2352)
at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

看的出来他们的继承关系是com.homelink.android.MyApplication继承com.ke.ljplugin.LjPluginApplication继承android.app.Application继承android.content.ContextWrapper继承android.content.Context

    var SecManager = Java.use("com.ke.securitylib.SecManager");
    console.log("com.ke.securitylib.SecManager.mContext--->" + SecManager.mContext.value)
    console.log(Java.use("com.homelink.android.MyApplication").class.getSuperclass().getName());
    console.log(Java.use("com.ke.ljplugin.LjPluginApplication").class.getSuperclass().getName());
    console.log(Java.use("android.app.Application").class.getSuperclass().getName());
    console.log(Java.use("android.content.ContextWrapper").class.getSuperclass().getName());

所以我们补的完整一些,这样是比较完整的

case "com/ke/securitylib/SecManager->mContext:Landroid/content/Context;": {
    return vm.resolveClass(
        "com/homelink/android/MyApplication",
        vm.resolveClass(
            "com/ke/ljplugin/LjPluginApplication",
            vm.resolveClass("android/app/Application",
                            vm.resolveClass("android.content.ContextWrapper"
                                            , vm.resolveClass("android.content.Context")))
        )
    ).newObject(null);
}

2.3 第二次初始化运行

android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;

java.lang.UnsupportedOperationException: android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$32.handle(DalvikVM64.java:559)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
        at com.github.unidbg.arm.backend.unicorn.Unicorn.emu_start(Native Method)

这个调用的是系统类的函数,所以直接补就行了

case "android/content/Context->getApplicationInfo()Landroid/content/pm/ApplicationInfo;":{
    return vm.resolveClass("android/content/pm/ApplicationInfo").newObject(null);
}

2.4 第三次初始化运行

android/content/pm/ApplicationInfo->className:Ljava/lang/String;

java.lang.UnsupportedOperationException: android/content/pm/ApplicationInfo->className:Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:73)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

我写一个frida打印常用数据的脚本  

function hook_2() {
    var SecManager = Java.use("com.ke.securitylib.SecManager");

    var ctx = SecManager.mContext.value;
    console.log("mContext = " + ctx);
    console.log("mContext class = " + ctx.getClass().getName());

    var appInfo = ctx.getApplicationInfo();
    console.log("ApplicationInfo = " + appInfo);

    console.log("packageName = " + appInfo.packageName.value);
    console.log("sourceDir = " + appInfo.sourceDir.value);
    console.log("publicSourceDir = " + appInfo.publicSourceDir.value);
    console.log("dataDir = " + appInfo.dataDir.value);
    console.log("nativeLibraryDir = " + appInfo.nativeLibraryDir.value);
    console.log("processName = " + appInfo.processName.value);
    console.log("className = " + appInfo.className.value);
    console.log("uid = " + appInfo.uid.value);
    console.log("flags = " + appInfo.flags.value);

    try {
        console.log("targetSdkVersion = " + appInfo.targetSdkVersion.value);
    } catch (e) {
        console.log("targetSdkVersion read failed: " + e);
    }

    try {
        console.log("minSdkVersion = " + appInfo.minSdkVersion.value);
    } catch (e) {
        console.log("minSdkVersion read failed: " + e);
    }
}

/*
mContext = com.homelink.android.MyApplication@666d274
mContext class = com.homelink.android.MyApplication
ApplicationInfo = ApplicationInfo{b7a9785 com.lianjia.beike}
packageName = com.lianjia.beike
sourceDir = /data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk
publicSourceDir = /data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk
dataDir = /data/user/0/com.lianjia.beike
nativeLibraryDir = /data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/lib/arm64
processName = com.lianjia.beike
className = com.homelink.android.MyApplication
uid = 10285
flags = 953695812
targetSdkVersion = 31
minSdkVersion = 23
*/

所以按照这个补就行了

case "android/content/pm/ApplicationInfo->className:Ljava/lang/String;":{
    return new StringObject(vm,"com.homelink.android.MyApplication");
}

2.5 第四次初始化运行

java/lang/Class->getPackage()Ljava/lang/Package;

java.lang.UnsupportedOperationException: java/lang/Class->getPackage()Ljava/lang/Package;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
        at com.project.beike.BeiKe3.callObjectMethodV(BeiKe3.java:63)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$32.handle(DalvikVM64.java:559)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这种更推荐动态的去补根据当前 Class 的类名动态生成 packageName

case "java/lang/Class->getPackage()Ljava/lang/Package;": {
    String className = ((DvmClass) dvmObject).getClassName();
    int index = className.lastIndexOf('/');
    String packageName = className.substring(0, index);
    return vm.resolveClass("java/lang/Package").newObject(packageName);
}

2.6 第五次初始化运行

java/lang/Package->getName()Ljava/lang/String;

java.lang.UnsupportedOperationException: java/lang/Package->getName()Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
        at com.project.beike.BeiKe3.callObjectMethodV(BeiKe3.java:66)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$32.handle(DalvikVM64.java:559)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

因为上面是动态补的,所以我们直接获取dvmObject.getValue()就行了

case "java/lang/Package->getName()Ljava/lang/String;": {
    return new StringObject(vm, String.valueOf(dvmObject.getValue()));
}

2.7 第六次初始化运行

com/homelink/android/BuildConfig->DEBUG:Z

java.lang.UnsupportedOperationException: com/homelink/android/BuildConfig->DEBUG:Z
        at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticBooleanField(AbstractJni.java:113)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticBooleanField(AbstractJni.java:108)
        at com.github.unidbg.linux.android.dvm.DvmField.getStaticBooleanField(DvmField.java:111)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$143.handle(DalvikVM64.java:2375)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
        at com.github.unidbg.arm.backend.unicorn.Unicorn.emu_start(Native Method)

这个大概可以看得出来确定是不是debug,不过还是hook看一下现在就很明确了补false

function hook_4() {
    var BuildConfig = Java.use("com.homelink.android.BuildConfig");
    console.log("BuildConfig.DEBUG = " + BuildConfig.DEBUG.value);
}

/*
BuildConfig.DEBUG = false
*/
case "com/homelink/android/BuildConfig->DEBUG:Z":{
    return false;
}

2.8 第七次初始化运行

android/app/ActivityThread->mBoundApplication:Landroid/app/ActivityThread$AppBindData;

java.lang.UnsupportedOperationException: android/app/ActivityThread->mBoundApplication:Landroid/app/ActivityThread$AppBindData;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:87)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这个可以直接补,因为是android 框架环境里面的

case "android/app/ActivityThread->mBoundApplication:Landroid/app/ActivityThread$AppBindData;": {
    return vm.resolveClass("android/app/ActivityThread$AppBindData").newObject(null);
}

2.9 第八次初始化运行

android/app/ActivityThread$AppBindData->info:Landroid/app/LoadedApk;

java.lang.UnsupportedOperationException: android/app/ActivityThread$AppBindData->info:Landroid/app/LoadedApk;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:90)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这样也一样

case "android/app/ActivityThread$AppBindData->info:Landroid/app/LoadedApk;":{
    return vm.resolveClass("android/app/LoadedApk").newObject(null);
}

2.10 第九次初始化运行

android/app/LoadedApk->mApplicationInfo:Landroid/content/pm/ApplicationInfo;

java.lang.UnsupportedOperationException: android/app/LoadedApk->mApplicationInfo:Landroid/content/pm/ApplicationInfo;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:93)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

继续补

case "android/app/LoadedApk->mApplicationInfo:Landroid/content/pm/ApplicationInfo;": {
    return vm.resolveClass("android/content/pm/ApplicationInfo").newObject(null);
}

2.11 第十次初始化运行

android/content/pm/ApplicationInfo->FLAG_EXTERNAL_STORAGE:I

java.lang.UnsupportedOperationException: android/content/pm/ApplicationInfo->FLAG_EXTERNAL_STORAGE:I
        at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticIntField(AbstractJni.java:136)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticIntField(AbstractJni.java:128)
        at com.github.unidbg.linux.android.dvm.DvmField.getStaticIntField(DvmField.java:121)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$147.handle(DalvikVM64.java:2435)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
        at com.github.unidbg.arm.backend.unicorn.Unicorn.emu_start(Native Method)

这个因为返回是int类型的所以可以用frida hook看看,结果是262144

function hook_5() {
    var ApplicationInfo = Java.use("android.content.pm.ApplicationInfo");
    console.log("ApplicationInfo.FLAG_EXTERNAL_STORAGE = " + ApplicationInfo.FLAG_EXTERNAL_STORAGE.value);
}

/*
ApplicationInfo.FLAG_EXTERNAL_STORAGE = 262144
*/
case "android/content/pm/ApplicationInfo->FLAG_EXTERNAL_STORAGE:I": {
    return 262144;
}

2.12 第十一次初始化运行

android/content/pm/ApplicationInfo->flags:I

java.lang.UnsupportedOperationException: android/content/pm/ApplicationInfo->flags:I
        at com.github.unidbg.linux.android.dvm.AbstractJni.getIntField(AbstractJni.java:648)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getIntField(AbstractJni.java:640)
        at com.github.unidbg.linux.android.dvm.DvmField.getIntField(DvmField.java:136)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$97.handle(DalvikVM64.java:1522)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
        at com.github.unidbg.arm.backend.unicorn.Unicorn.emu_start(Native Method)

这个值在上面是打印过的 flags = 953695812

case "android/content/pm/ApplicationInfo->flags:I":{
    return 953695812;
}

2.13 第十二次初始化运行

android/app/LoadedApk->mDataDirFile:Ljava/io/File;

java.lang.UnsupportedOperationException: android/app/LoadedApk->mDataDirFile:Ljava/io/File;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:96)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这个我们还是先hook看看,返回的是/data/user/0/com.lianjia.beike

function hook_7() {
  var ActivityThread = Java.use("android.app.ActivityThread");

  var at = ActivityThread.currentActivityThread();
  console.log("ActivityThread = " + at);

  var atCls = at.getClass();

  var fBound = atCls.getDeclaredField("mBoundApplication");
  fBound.setAccessible(true);
  var bound = fBound.get(at);
  console.log("mBoundApplication = " + bound);

  var boundCls = bound.getClass();

  var fInfo = boundCls.getDeclaredField("info");
  fInfo.setAccessible(true);
  var loadedApk = fInfo.get(bound);
  console.log("LoadedApk = " + loadedApk);
  console.log("LoadedApk class = " + loadedApk.getClass().getName());

  var loadedApkCls = loadedApk.getClass();

  function dumpField(name) {
    try {
      var f = loadedApkCls.getDeclaredField(name);
      f.setAccessible(true);
      var v = f.get(loadedApk);
      console.log(name + " = " + v);

      if (v != null && v.getClass().getName() === "java.io.File") {
        console.log(name + ".getPath() = " + v.getPath());
        console.log(name + ".getAbsolutePath() = " + v.getAbsolutePath());
        console.log(name + ".getCanonicalPath() = " + v.getCanonicalPath());
      }
    } catch (e) {
      console.log(name + " read failed: " + e);
    }
  }

  dumpField("mDataDir");
  dumpField("mDataDirFile");
  dumpField("mAppDir");
  dumpField("mResDir");
  dumpField("mLibDir");
  dumpField("mPackageName");
  dumpField("mApplicationInfo");
}

/*
ActivityThread = android.app.ActivityThread@e265bc7
mBoundApplication = AppBindData{appInfo=ApplicationInfo{935e5f4 com.lianjia.beike}}
LoadedApk = android.app.LoadedApk@6bb711d  
LoadedApk class = android.app.LoadedApk    
mDataDir = /data/user/0/com.lianjia.beike  
mDataDirFile = /data/user/0/com.lianjia.beike
mDataDirFile read failed: TypeError: not a function
mAppDir = /data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk
mResDir = /data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk
mLibDir = /data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/lib/arm64
mPackageName = com.lianjia.beike
mApplicationInfo = ApplicationInfo{935e5f4 com.lianjia.beike}
*/
case "android/app/LoadedApk->mDataDirFile:Ljava/io/File;": {
    return vm.resolveClass("java/io/File").newObject("/data/user/0/com.lianjia.beike");
}

2.14 第十三次初始化运行

java/io/File->getCanonicalPath()Ljava/lang/String;

java.lang.UnsupportedOperationException: java/io/File->getCanonicalPath()Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:417)
        at com.project.beike.BeiKe3.callObjectMethodV(BeiKe3.java:77)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:262)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:89)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$32.handle(DalvikVM64.java:559)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这个直接返回 Value 就行了

case "java/io/File->getCanonicalPath()Ljava/lang/String;": {
    return new StringObject(vm, String.valueOf(dvmObject.getValue()));
}

2.15 第十四次初始化运行

android/app/LoadedApk->mPackageName:Ljava/lang/String;

java.lang.UnsupportedOperationException: android/app/LoadedApk->mPackageName:Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:104)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这个前面的frida hook已经是有输出这个的,按照hook的返回就行

case "android/app/LoadedApk->mPackageName:Ljava/lang/String;":{
    return new StringObject(vm,"com.lianjia.beike");
}

2.16 第十五次初始化

android/app/LoadedApk->mResDir:Ljava/lang/String;

java.lang.UnsupportedOperationException: android/app/LoadedApk->mResDir:Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)
        at com.project.beike.BeiKe3.getObjectField(BeiKe3.java:106)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:141)
        at com.github.unidbg.linux.android.dvm.DvmField.getObjectField(DvmField.java:126)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$92.handle(DalvikVM64.java:1436)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

这个也是有返回的

case "android/app/LoadedApk->mResDir:Ljava/lang/String;":{
    return new StringObject(vm,"/data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk");
}

2.17 初始化完成

这个初始化总体来说不难,主要就是

  1. 当前进程名 / 包名是否是 com.lianjia.beike
  2. SecManager.mContext 是否是正常 Application Context
  3. Application 类名是否是 com.homelink.android.MyApplication
  4. BuildConfig.DEBUG 是否为 false
  5. ApplicationInfo.flags 以及是否外部存储安装
  6. ActivityThread / LoadedApk 内部环境是否正常
  7. App 数据目录路径 /data/user/0/com.lianjia.beike
  8. APK 资源路径 base.apk
  9. 类 package 信息
JNIEnv->FindClass(com/ke/securitylib/SecManager) was called from RX@0x1201203c[libkmsec.so]0x1203c
JNIEnv->GetStaticFieldID(com/ke/securitylib/SecManager.mContextLandroid/content/Context;) => 0x957413d4 was called from RX@0x120121c0[libkmsec.so]0x121c0
JNIEnv->GetStaticObjectField(class com/ke/securitylib/SecManager, mContext Landroid/content/Context; => com.homelink.android.MyApplication@33d512c1) was called from RX@0x120121d8[libkmsec.so]0x121d8
JNIEnv->NewGlobalRef(com.homelink.android.MyApplication@33d512c1) was called from RX@0x120121f0[libkmsec.so]0x121f0
JNIEnv->FindClass(android/content/Context) was called from RX@0x120122f4[libkmsec.so]0x122f4
JNIEnv->GetMethodID(android/content/Context.getApplicationInfo()Landroid/content/pm/ApplicationInfo;) => 0xbfeda400 was called from RX@0x12012464[libkmsec.so]0x12464
JNIEnv->CallObjectMethodV(com.homelink.android.MyApplication@33d512c1, getApplicationInfo() => android.content.pm.ApplicationInfo@765d7657) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(android/content/pm/ApplicationInfo) was called from RX@0x12012510[libkmsec.so]0x12510
JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.className Ljava/lang/String;) => 0x6b2d0d54 was called from RX@0x1201269c[libkmsec.so]0x1269c
JNIEnv->GetObjectField(android.content.pm.ApplicationInfo@765d7657, className Ljava/lang/String; => "com.homelink.android.MyApplication") was called from RX@0x120126b4[libkmsec.so]0x126b4
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("com.homelink.android.MyApplication", getBytes("utf-8") => [B@0x636f6d2e686f6d656c696e6b2e616e64726f69642e4d794170706c69636174696f6e) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x636f6d2e686f6d656c696e6b2e616e64726f69642e4d794170706c69636174696f6e => 34) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x636f6d2e686f6d656c696e6b2e616e64726f69642e4d794170706c69636174696f6e was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(com/homelink/android/MyApplication) was called from RX@0x12012724[libkmsec.so]0x12724
JNIEnv->FindClass(java/lang/Class) was called from RX@0x12012864[libkmsec.so]0x12864
JNIEnv->GetMethodID(java/lang/Class.getPackage()Ljava/lang/Package;) => 0x4ea0dfef was called from RX@0x12012a00[libkmsec.so]0x12a00
Class.getPackage className = com/homelink/android/MyApplication, packageName = com/homelink/android
JNIEnv->CallObjectMethodV(class com/homelink/android/MyApplication, getPackage() => java.lang.Package@34e9fd99) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(java/lang/Package) was called from RX@0x12012a94[libkmsec.so]0x12a94
JNIEnv->GetMethodID(java/lang/Package.getName()Ljava/lang/String;) => 0x9bd1ab69 was called from RX@0x12012c00[libkmsec.so]0x12c00
JNIEnv->CallObjectMethodV(java.lang.Package@34e9fd99, getName() => "com/homelink/android") was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("com/homelink/android", getBytes("utf-8") => [B@0x636f6d2f686f6d656c696e6b2f616e64726f6964) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x636f6d2f686f6d656c696e6b2f616e64726f6964 => 20) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x636f6d2f686f6d656c696e6b2f616e64726f6964 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(com/homelink/android/BuildConfig) was called from RX@0x12012e64[libkmsec.so]0x12e64
JNIEnv->GetStaticFieldID(com/homelink/android/BuildConfig.DEBUGZ) => 0x4fb24b5a was called from RX@0x12012fe4[libkmsec.so]0x12fe4
JNIEnv->GetStaticBooleanField(class com/homelink/android/BuildConfig, DEBUG => false) was called from RX@0x1201302c[libkmsec.so]0x1302c
JNIEnv->FindClass(android/app/ActivityThread) was called from RX@0x12011648[libkmsec.so]0x11648
JNIEnv->GetStaticMethodID(android/app/ActivityThread.currentActivityThread()Landroid/app/ActivityThread;) => 0xf7e11563 was called from RX@0x12011774[libkmsec.so]0x11774
JNIEnv->CallStaticObjectMethodV(class android/app/ActivityThread, currentActivityThread() => android.app.ActivityThread@43bc63a3) was called from RX@0x120114e8[libkmsec.so]0x114e8
JNIEnv->GetFieldID(android/app/ActivityThread.mBoundApplication Landroid/app/ActivityThread$AppBindData;) => 0x82f229d7 was called from RX@0x1201194c[libkmsec.so]0x1194c
JNIEnv->GetObjectField(android.app.ActivityThread@43bc63a3, mBoundApplication Landroid/app/ActivityThread$AppBindData; => android.app.ActivityThread$AppBindData@702657cc) was called from RX@0x12011964[libkmsec.so]0x11964
JNIEnv->FindClass(android/app/ActivityThread$AppBindData) was called from RX@0x12011a74[libkmsec.so]0x11a74
JNIEnv->GetFieldID(android/app/ActivityThread$AppBindData.info Landroid/app/LoadedApk;) => 0x820f6e0e was called from RX@0x12011bb8[libkmsec.so]0x11bb8
JNIEnv->GetObjectField(android.app.ActivityThread$AppBindData@702657cc, info Landroid/app/LoadedApk; => android.app.LoadedApk@6a6cb05c) was called from RX@0x12011bd0[libkmsec.so]0x11bd0
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x120131d8[libkmsec.so]0x131d8
JNIEnv->GetFieldID(android/app/LoadedApk.mApplicationInfo Landroid/content/pm/ApplicationInfo;) => 0xd2eea0dc was called from RX@0x12013218[libkmsec.so]0x13218
JNIEnv->GetObjectField(android.app.LoadedApk@6a6cb05c, mApplicationInfo Landroid/content/pm/ApplicationInfo; => android.content.pm.ApplicationInfo@40a4337a) was called from RX@0x1201324c[libkmsec.so]0x1324c
JNIEnv->FindClass(android/content/pm/ApplicationInfo) was called from RX@0x120132a4[libkmsec.so]0x132a4
JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.flags I) => 0xd8f8f0a3 was called from RX@0x120132cc[libkmsec.so]0x132cc
JNIEnv->GetStaticFieldID(android/content/pm/ApplicationInfo.FLAG_EXTERNAL_STORAGEI) => 0x1ce01db6 was called from RX@0x1201330c[libkmsec.so]0x1330c
JNIEnv->GetStaticIntField(class android/content/pm/ApplicationInfo, FLAG_EXTERNAL_STORAGE => 0x40000) was called from RX@0x12013340[libkmsec.so]0x13340
JNIEnv->GetIntField(android.content.pm.ApplicationInfo@40a4337a, flags => 0x38d83e44) was called from RX@0x12013374[libkmsec.so]0x13374
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x12013444[libkmsec.so]0x13444
JNIEnv->GetFieldID(android/app/LoadedApk.mDataDirFile Ljava/io/File;) => 0xc3cdbda5 was called from RX@0x12013484[libkmsec.so]0x13484
JNIEnv->GetObjectField(android.app.LoadedApk@6a6cb05c, mDataDirFile Ljava/io/File; => java.io.File@6025e1b6) was called from RX@0x120134b8[libkmsec.so]0x134b8
JNIEnv->FindClass(java/io/File) was called from RX@0x1201351c[libkmsec.so]0x1351c
JNIEnv->GetMethodID(java/io/File.getCanonicalPath()Ljava/lang/String;) => 0x25f5f2f7 was called from RX@0x12013544[libkmsec.so]0x13544
JNIEnv->CallObjectMethodV(java.io.File@6025e1b6, getCanonicalPath() => "/data/user/0/com.lianjia.beike") was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("/data/user/0/com.lianjia.beike", getBytes("utf-8") => [B@0x2f646174612f757365722f302f636f6d2e6c69616e6a69612e6265696b65) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x2f646174612f757365722f302f636f6d2e6c69616e6a69612e6265696b65 => 30) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x2f646174612f757365722f302f636f6d2e6c69616e6a69612e6265696b65 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x12013604[libkmsec.so]0x13604
JNIEnv->GetFieldID(android/app/LoadedApk.mPackageName Ljava/lang/String;) => 0xef0890a6 was called from RX@0x1201365c[libkmsec.so]0x1365c
JNIEnv->GetObjectField(android.app.LoadedApk@6a6cb05c, mPackageName Ljava/lang/String; => "com.lianjia.beike") was called from RX@0x1201367c[libkmsec.so]0x1367c
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("com.lianjia.beike", getBytes("utf-8") => [B@0x636f6d2e6c69616e6a69612e6265696b65) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x636f6d2e6c69616e6a69612e6265696b65 => 17) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x636f6d2e6c69616e6a69612e6265696b65 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x12011d0c[libkmsec.so]0x11d0c
JNIEnv->GetFieldID(android/app/LoadedApk.mResDir Ljava/lang/String;) => 0x1c8f9084 was called from RX@0x12011e90[libkmsec.so]0x11e90
JNIEnv->GetObjectField(android.app.LoadedApk@6a6cb05c, mResDir Ljava/lang/String; => "/data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk") was called from RX@0x12011edc[libkmsec.so]0x11edc
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("/data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk", getBytes("utf-8") => [B@3b69e7d1) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@3b69e7d1 => 88) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@3b69e7d1 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(com/ke/securitylib/SecManager) was called from RX@0x12013c48[libkmsec.so]0x13c48
JNIEnv->RegisterNatives(com/ke/securitylib/SecManager, RW@0x12049e10[libkmsec.so]0x49e10, 1) was called from RX@0x12013c6c[libkmsec.so]0x13c6c
RegisterNative(com/ke/securitylib/SecManager, sign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;, RX@0x12017d00[libkmsec.so]0x17d00)

2.18 调用目标函数

事前用frida hook了函数的入参和返回,现在开始在unidbg中正式的调用目标函数

SecManager.sign is called:
str=accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14,
str2=sjoe98HI099dhdD7,
str3=1782806580

SecManager.sign result=C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513,28B22DAC5AAD6BFBDBFD2DBB4F40E878,accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14,sjoe98HI099dhdD7,1782806580,sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580,108C29105573A8DE6A342C56FADC472A,108C29105573A8DE6A342C56FADC472A,C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513
public void callSign() {
    String str = "accessKeyId=sjoe98HI099dhdD7" +
            "&appinfo-s=Beike;3.04.31;3043100" +
            "&channel-s=Android_ke_vivo" +
            "&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;" +
            "&hardware-s=google;Pixel 6" +
            "&host=apps.api.ke.com" +
            "&method=GET" +
            "&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index" +
            "&query=city_id=510100" +
            "&pre_request=1" +
            "&request_ts=1782806580" +
            "&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s" +
            "&systeminfo-s=android;14" +
            "×tamp=1782806580" +
            "&user-agent=Beike3.04.31;google Pixel+6; Android 14";
    String str1 = "sjoe98HI099dhdD7";
    String str2 = "1782806580";
    System.out.println(str);
    DvmObject<?> dvmObject = SecManager.callStaticJniMethodObject(emulator,
            "sign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;",
            str,
            str1,
            str2
    );
    System.out.println(dvmObject.getValue());
}

2.19 第一次补目标函数

com/ke/securitylib/SecManager->getCertificateMD5Digest()Ljava/lang/String;

看这个报错可以看得出来这个类就是我们调用的函数的类,我们先去jadx中看看

java.lang.UnsupportedOperationException: com/ke/securitylib/SecManager->getCertificateMD5Digest()Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:504)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:438)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethodV(DvmMethod.java:59)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$112.handle(DalvikVM64.java:1836)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
        at com.github.unidbg.arm.backend.unicorn.Unicorn.emu_start(Native Method)

mCertificateMD5Digest是通过host.ar0.a.p50(mContext)toUpperCase生成的p50函数中则是获取当前 App 安装包签名证书的 MD5 值,并返回 32 位小写十六进制字符串  

public static String getCertificateMD5Digest() {
    PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[0], null, changeQuickRedirect, true, 90805, new Class[0], String.class);
    if (patchProxyResultProxy.isSupported) {
        return (String) patchProxyResultProxy.result;
    }
    if (TextUtils.isEmpty(mCertificateMD5Digest)) {
        String strP50 = host.ar0.a.p50(mContext);
        if (!TextUtils.isEmpty(strP50)) {
            mCertificateMD5Digest = strP50.toUpperCase();
        }
    }
    return mCertificateMD5Digest;
}

public static String p50(Context context) {
    PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{context}, null, changeQuickRedirect, true, 75772, new Class[]{Context.class}, String.class);
    if (patchProxyResultProxy.isSupported) {
        return (String) patchProxyResultProxy.result;
    }
    try {
        Signature[] signatureArr = context.getPackageManager().getPackageInfo(context.getPackageName(), 64).signatures;
        if (signatureArr.length <= 0) {
            return "";
        }
        Signature signature = signatureArr[0];
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        messageDigest.update(signature.toByteArray());
        byte[] bArrDigest = messageDigest.digest();
        StringBuilder sb = new StringBuilder();
        for (byte b : bArrDigest) {
            String hexString = Integer.toHexString(b & 255);
            if (hexString.length() == 1) {
                sb.append("0");
            }
            sb.append(hexString);
        }
        return sb.toString();
    } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
        KeLog.z50(e);
        return "";
    }
}

我让ai给我写了一个java脚本,输出的结果是cert md5 = 28b22dac5aad6bfbdbfd2dbb4f40e878

package com.project.beike;

import java.io.File;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ApkCertMd5 {
    public static void main(String[] args) throws Exception {
        String apkPath = "mkzfv3.04.31.apk";

        JarFile jarFile = new JarFile(new File(apkPath));
        byte[] buffer = new byte[8192];

        Certificate[] certs = null;

        for (JarEntry entry : java.util.Collections.list(jarFile.entries())) {
            if (entry.isDirectory()) {
                continue;
            }

            String name = entry.getName();
            if (name.startsWith("META-INF/")) {
                continue;
            }

            try (InputStream is = jarFile.getInputStream(entry)) {
                while (is.read(buffer) != -1) {
                    // 必须把 entry 读完,证书才会被校验并加载
                }
            }

            certs = entry.getCertificates();
            if (certs != null && certs.length > 0) {
                break;
            }
        }

        jarFile.close();

        if (certs == null || certs.length == 0) {
            System.out.println("没有读取到 v1 签名证书,建议用 apksigner");
            return;
        }

        byte[] certBytes = certs[0].getEncoded();

        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] digest = md.digest(certBytes);

        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            String hex = Integer.toHexString(b & 0xff);
            if (hex.length() == 1) {
                sb.append("0");
            }
            sb.append(hex);
        }

        System.out.println("cert md5 = " + sb.toString());
    }
}

用frida的hook结果也是一致的a.p50 result=28b22dac5aad6bfbdbfd2dbb4f40e878

function hook_9() {
  var a = Java.use("host.ar0.a");
  a["p50"].implementation = function (context) {
    console.log(`a.p50 is called: context=${context}`);
    let result = this["p50"](context);
    console.log(`a.p50 result=${result}`);
    return result;
  };
}

/*
a.p50 result=28b22dac5aad6bfbdbfd2dbb4f40e878
a.p50 result=28b22dac5aad6bfbdbfd2dbb4f40e878
a.p50 result=28b22dac5aad6bfbdbfd2dbb4f40e878
*/

所以返回这个值就可以了

case "com/ke/securitylib/SecManager->getCertificateMD5Digest()Ljava/lang/String;":{
    return new StringObject(vm,"28B22DAC5AAD6BFBDBFD2DBB4F40E878");
}

2.20 第二次补目标函数

com/ke/infrastructrue/app/signature/util/MD5Utils->getMd5(Ljava/lang/String;)Ljava/lang/String;

看的的函数名称像是传入一个字符串,返回一个字符串md5的值,去jadx瞧瞧

java.lang.UnsupportedOperationException: com/ke/infrastructrue/app/signature/util/MD5Utils->getMd5(Ljava/lang/String;)Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:504)
        at com.project.beike.BeiKe3.callStaticObjectMethodV(BeiKe3.java:187)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:438)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethodV(DvmMethod.java:59)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$112.handle(DalvikVM64.java:1836)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:119)
        at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
        at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)

和我们的猜想一致,并且用的也是官方库,那就好补了

public static String getMd5(String str) {
    PatchProxyResult patchProxyResultProxy = PatchProxy.proxy(new Object[]{str}, null, changeQuickRedirect, true, 75784, new Class[]{String.class}, String.class);
    if (patchProxyResultProxy.isSupported) {
        return (String) patchProxyResultProxy.result;
    }
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        messageDigest.update(str.getBytes());
        byte[] bArrDigest = messageDigest.digest();
        StringBuilder sb = new StringBuilder();
        for (byte b : bArrDigest) {
            String hexString = Integer.toHexString(b & 255);
            while (hexString.length() < 2) {
                hexString = "0" + hexString;
            }
            sb.append(hexString);
        }
        return sb.toString().toUpperCase();
    } catch (Throwable th) {
        KeLog.z50(th);
        th.printStackTrace();
        return "";
    }
}
private static String md5Upper(String str) {
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        byte[] digest = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));

        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            String hexString = Integer.toHexString(b & 0xff);
            while (hexString.length() < 2) {
                hexString = "0" + hexString;
            }
            sb.append(hexString);
        }

        return sb.toString().toUpperCase();
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}

case "com/ke/infrastructrue/app/signature/util/MD5Utils->getMd5(Ljava/lang/String;)Ljava/lang/String;": {
    String str = vaList.getObjectArg(0).getValue().toString();
    return new StringObject(vm, md5Upper(str));
}

2.21 第三次补目标函数

直接报错了,这个错误是native 里面调用了 free(0x12085000),但是这个地址不是通过 malloc/calloc/realloc 分配出来的,所以 libc 判定非法释放,然后 native 崩了。native 崩了以后,你 Java 层 callSign() 拿到的是 null,最后又触发了 NPE  

Invalid address 0x12085000 passed to free: value not allocated
[crash]A/libc: Invalid address 0x12085000 passed to free: value not allocated
Exception in thread "main" java.lang.NullPointerException
        at com.project.beike.BeiKe3.callSign(BeiKe3.java:81)
        at com.project.beike.BeiKe3.main(BeiKe3.java:110)

写一个打印所有free的脚本打印他们的地址,最后一个输出的一定就是崩溃的free地址

private void hookFree() {
        Module libc = emulator.getMemory().findModule("libc.so");
        Symbol free = libc.findSymbolByName("free");

        emulator.attach().addBreakPoint(free.getAddress(), (emulator, address) -> {
            RegisterContext ctx = emulator.getContext();

            long ptr = ctx.getLongArg(0);
            long lr = ctx.getLRPointer().peer;

            System.out.println("========== free called ==========");
            System.out.printf("free ptr = 0x%x%n", ptr);
            System.out.printf("LR       = 0x%x%n", lr);

            if (kmsecModule != null) {
                long off = lr - kmsecModule.base;
                System.out.printf("libkmsec.so offset LR     = 0x%x%n", off);
                System.out.printf("libkmsec.so call site     = 0x%x%n", off - 4);
            }

            return true;
        });
    }

最后输出的地址是0x182cc

========== free called ==========
free ptr = 0x12085000
LR       = 0x120182cc
libkmsec.so offset LR     = 0x182cc
libkmsec.so call site     = 0x182c8
exit with code: 1

所以去ida看看是怎么回事, 上面红框是 getMd5_result 指针的来源,下面红框是非法释放点;问题就是 GetStringUTFChars 返回的指针不应该用 free() 释放。  所以我目前的解决方式是把他给nop了

0x182c8这个偏移刚好就是崩溃的free位置,所以只需要在这条指令还没有执行的时候attach在把他的PC+4,他执行的时候自然就指向0x182cc了.

private void nopFree(){
    long addr = kmsecModule.base + 0x182C8;

    emulator.attach().addBreakPoint(addr, (emulator, address) -> {
        System.out.printf("[skip call site] 0x%x -> 0x%x%n", address, address + 4);

        emulator.getBackend().reg_write(
                Arm64Const.UC_ARM64_REG_PC,
                address + 4
        );

        return true;
    });
}

2.22 完成补环境输出结果与hook结果对比

JNIEnv->FindClass(com/ke/securitylib/SecManager) was called from RX@0x1201203c[libkmsec.so]0x1203c
JNIEnv->GetStaticFieldID(com/ke/securitylib/SecManager.mContextLandroid/content/Context;) => 0x957413d4 was called from RX@0x120121c0[libkmsec.so]0x121c0
JNIEnv->GetStaticObjectField(class com/ke/securitylib/SecManager, mContext Landroid/content/Context; => com.homelink.android.MyApplication@2d1ef81a) was called from RX@0x120121d8[libkmsec.so]0x121d8
JNIEnv->NewGlobalRef(com.homelink.android.MyApplication@2d1ef81a) was called from RX@0x120121f0[libkmsec.so]0x121f0
JNIEnv->FindClass(android/content/Context) was called from RX@0x120122f4[libkmsec.so]0x122f4
JNIEnv->GetMethodID(android/content/Context.getApplicationInfo()Landroid/content/pm/ApplicationInfo;) => 0xbfeda400 was called from RX@0x12012464[libkmsec.so]0x12464
JNIEnv->CallObjectMethodV(com.homelink.android.MyApplication@2d1ef81a, getApplicationInfo() => android.content.pm.ApplicationInfo@4c12331b) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(android/content/pm/ApplicationInfo) was called from RX@0x12012510[libkmsec.so]0x12510
JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.className Ljava/lang/String;) => 0x6b2d0d54 was called from RX@0x1201269c[libkmsec.so]0x1269c
JNIEnv->GetObjectField(android.content.pm.ApplicationInfo@4c12331b, className Ljava/lang/String; => "com.homelink.android.MyApplication") was called from RX@0x120126b4[libkmsec.so]0x126b4
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("com.homelink.android.MyApplication", getBytes("utf-8") => [B@0x636f6d2e686f6d656c696e6b2e616e64726f69642e4d794170706c69636174696f6e) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x636f6d2e686f6d656c696e6b2e616e64726f69642e4d794170706c69636174696f6e => 34) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x636f6d2e686f6d656c696e6b2e616e64726f69642e4d794170706c69636174696f6e was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(com/homelink/android/MyApplication) was called from RX@0x12012724[libkmsec.so]0x12724
JNIEnv->FindClass(java/lang/Class) was called from RX@0x12012864[libkmsec.so]0x12864
JNIEnv->GetMethodID(java/lang/Class.getPackage()Ljava/lang/Package;) => 0x4ea0dfef was called from RX@0x12012a00[libkmsec.so]0x12a00
Class.getPackage className = com/homelink/android/MyApplication, packageName = com/homelink/android
JNIEnv->CallObjectMethodV(class com/homelink/android/MyApplication, getPackage() => java.lang.Package@f5958c9) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(java/lang/Package) was called from RX@0x12012a94[libkmsec.so]0x12a94
JNIEnv->GetMethodID(java/lang/Package.getName()Ljava/lang/String;) => 0x9bd1ab69 was called from RX@0x12012c00[libkmsec.so]0x12c00
JNIEnv->CallObjectMethodV(java.lang.Package@f5958c9, getName() => "com/homelink/android") was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("com/homelink/android", getBytes("utf-8") => [B@0x636f6d2f686f6d656c696e6b2f616e64726f6964) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x636f6d2f686f6d656c696e6b2f616e64726f6964 => 20) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x636f6d2f686f6d656c696e6b2f616e64726f6964 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(com/homelink/android/BuildConfig) was called from RX@0x12012e64[libkmsec.so]0x12e64
JNIEnv->GetStaticFieldID(com/homelink/android/BuildConfig.DEBUGZ) => 0x4fb24b5a was called from RX@0x12012fe4[libkmsec.so]0x12fe4
JNIEnv->GetStaticBooleanField(class com/homelink/android/BuildConfig, DEBUG => false) was called from RX@0x1201302c[libkmsec.so]0x1302c
JNIEnv->FindClass(android/app/ActivityThread) was called from RX@0x12011648[libkmsec.so]0x11648
JNIEnv->GetStaticMethodID(android/app/ActivityThread.currentActivityThread()Landroid/app/ActivityThread;) => 0xf7e11563 was called from RX@0x12011774[libkmsec.so]0x11774
JNIEnv->CallStaticObjectMethodV(class android/app/ActivityThread, currentActivityThread() => android.app.ActivityThread@1e800aaa) was called from RX@0x120114e8[libkmsec.so]0x114e8
JNIEnv->GetFieldID(android/app/ActivityThread.mBoundApplication Landroid/app/ActivityThread$AppBindData;) => 0x82f229d7 was called from RX@0x1201194c[libkmsec.so]0x1194c
JNIEnv->GetObjectField(android.app.ActivityThread@1e800aaa, mBoundApplication Landroid/app/ActivityThread$AppBindData; => android.app.ActivityThread$AppBindData@185a6e9) was called from RX@0x12011964[libkmsec.so]0x11964
JNIEnv->FindClass(android/app/ActivityThread$AppBindData) was called from RX@0x12011a74[libkmsec.so]0x11a74
JNIEnv->GetFieldID(android/app/ActivityThread$AppBindData.info Landroid/app/LoadedApk;) => 0x820f6e0e was called from RX@0x12011bb8[libkmsec.so]0x11bb8
JNIEnv->GetObjectField(android.app.ActivityThread$AppBindData@185a6e9, info Landroid/app/LoadedApk; => android.app.LoadedApk@6f03482) was called from RX@0x12011bd0[libkmsec.so]0x11bd0
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x120131d8[libkmsec.so]0x131d8
JNIEnv->GetFieldID(android/app/LoadedApk.mApplicationInfo Landroid/content/pm/ApplicationInfo;) => 0xd2eea0dc was called from RX@0x12013218[libkmsec.so]0x13218
JNIEnv->GetObjectField(android.app.LoadedApk@6f03482, mApplicationInfo Landroid/content/pm/ApplicationInfo; => android.content.pm.ApplicationInfo@9d5509a) was called from RX@0x1201324c[libkmsec.so]0x1324c
JNIEnv->FindClass(android/content/pm/ApplicationInfo) was called from RX@0x120132a4[libkmsec.so]0x132a4
JNIEnv->GetFieldID(android/content/pm/ApplicationInfo.flags I) => 0xd8f8f0a3 was called from RX@0x120132cc[libkmsec.so]0x132cc
JNIEnv->GetStaticFieldID(android/content/pm/ApplicationInfo.FLAG_EXTERNAL_STORAGEI) => 0x1ce01db6 was called from RX@0x1201330c[libkmsec.so]0x1330c
JNIEnv->GetStaticIntField(class android/content/pm/ApplicationInfo, FLAG_EXTERNAL_STORAGE => 0x40000) was called from RX@0x12013340[libkmsec.so]0x13340
JNIEnv->GetIntField(android.content.pm.ApplicationInfo@9d5509a, flags => 0x38d83e44) was called from RX@0x12013374[libkmsec.so]0x13374
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x12013444[libkmsec.so]0x13444
JNIEnv->GetFieldID(android/app/LoadedApk.mDataDirFile Ljava/io/File;) => 0xc3cdbda5 was called from RX@0x12013484[libkmsec.so]0x13484
JNIEnv->GetObjectField(android.app.LoadedApk@6f03482, mDataDirFile Ljava/io/File; => java.io.File@179ece50) was called from RX@0x120134b8[libkmsec.so]0x134b8
JNIEnv->FindClass(java/io/File) was called from RX@0x1201351c[libkmsec.so]0x1351c
JNIEnv->GetMethodID(java/io/File.getCanonicalPath()Ljava/lang/String;) => 0x25f5f2f7 was called from RX@0x12013544[libkmsec.so]0x13544
JNIEnv->CallObjectMethodV(java.io.File@179ece50, getCanonicalPath() => "/data/user/0/com.lianjia.beike") was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("/data/user/0/com.lianjia.beike", getBytes("utf-8") => [B@0x2f646174612f757365722f302f636f6d2e6c69616e6a69612e6265696b65) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x2f646174612f757365722f302f636f6d2e6c69616e6a69612e6265696b65 => 30) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x2f646174612f757365722f302f636f6d2e6c69616e6a69612e6265696b65 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x12013604[libkmsec.so]0x13604
JNIEnv->GetFieldID(android/app/LoadedApk.mPackageName Ljava/lang/String;) => 0xef0890a6 was called from RX@0x1201365c[libkmsec.so]0x1365c
JNIEnv->GetObjectField(android.app.LoadedApk@6f03482, mPackageName Ljava/lang/String; => "com.lianjia.beike") was called from RX@0x1201367c[libkmsec.so]0x1367c
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("com.lianjia.beike", getBytes("utf-8") => [B@0x636f6d2e6c69616e6a69612e6265696b65) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@0x636f6d2e6c69616e6a69612e6265696b65 => 17) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@0x636f6d2e6c69616e6a69612e6265696b65 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(android/app/LoadedApk) was called from RX@0x12011d0c[libkmsec.so]0x11d0c
JNIEnv->GetFieldID(android/app/LoadedApk.mResDir Ljava/lang/String;) => 0x1c8f9084 was called from RX@0x12011e90[libkmsec.so]0x11e90
JNIEnv->GetObjectField(android.app.LoadedApk@6f03482, mResDir Ljava/lang/String; => "/data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk") was called from RX@0x12011edc[libkmsec.so]0x11edc
JNIEnv->FindClass(java/lang/String) was called from RX@0x12010fa0[libkmsec.so]0x10fa0
JNIEnv->NewStringUTF("utf-8") was called from RX@0x12011070[libkmsec.so]0x11070
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x120111f4[libkmsec.so]0x111f4
JNIEnv->CallObjectMethodV("/data/app/~~IhB8QxXUoPHo99oLRlVbKQ==/com.lianjia.beike-R8_lhA9Y-mLCRuMQ0T1I7g==/base.apk", getBytes("utf-8") => [B@1a677343) was called from RX@0x12011358[libkmsec.so]0x11358
JNIEnv->GetArrayLength([B@1a677343 => 88) was called from RX@0x12011220[libkmsec.so]0x11220
JNIEnv->GetByteArrayElements(false) => [B@1a677343 was called from RX@0x1201123c[libkmsec.so]0x1123c
JNIEnv->FindClass(com/ke/securitylib/SecManager) was called from RX@0x12013c48[libkmsec.so]0x13c48
JNIEnv->RegisterNatives(com/ke/securitylib/SecManager, RW@0x12049e10[libkmsec.so]0x49e10, 1) was called from RX@0x12013c6c[libkmsec.so]0x13c6c
RegisterNative(com/ke/securitylib/SecManager, sign(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;, RX@0x12017d00[libkmsec.so]0x17d00)
Find native function Java_com_ke_securitylib_SecManager_sign => RX@0x12017d00[libkmsec.so]0x17d00
JNIEnv->GetStringUtfChars("sjoe98HI099dhdD7") was called from RX@0x12017d54[libkmsec.so]0x17d54
JNIEnv->FindClass(com/ke/securitylib/SecManager) was called from RX@0x12017df0[libkmsec.so]0x17df0
JNIEnv->GetStaticMethodID(com/ke/securitylib/SecManager.getCertificateMD5Digest()Ljava/lang/String;) => 0x91e5f2e was called from RX@0x12017e18[libkmsec.so]0x17e18
JNIEnv->CallStaticObjectMethodV(class com/ke/securitylib/SecManager, getCertificateMD5Digest() => "28B22DAC5AAD6BFBDBFD2DBB4F40E878") was called from RX@0x120114e8[libkmsec.so]0x114e8
JNIEnv->GetStringUtfChars("28B22DAC5AAD6BFBDBFD2DBB4F40E878") was called from RX@0x12017e40[libkmsec.so]0x17e40
JNIEnv->GetStringUtfChars("1782806580") was called from RX@0x12017f30[libkmsec.so]0x17f30
JNIEnv->GetStringUtfChars("accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14") was called from RX@0x12017f68[libkmsec.so]0x17f68
JNIEnv->FindClass(com/ke/infrastructrue/app/signature/util/MD5Utils) was called from RX@0x12017fbc[libkmsec.so]0x17fbc
JNIEnv->GetStaticMethodID(com/ke/infrastructrue/app/signature/util/MD5Utils.getMd5(Ljava/lang/String;)Ljava/lang/String;) => 0x234fea10 was called from RX@0x12017fe4[libkmsec.so]0x17fe4
JNIEnv->NewStringUTF("sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580") was called from RX@0x12017ffc[libkmsec.so]0x17ffc
JNIEnv->CallStaticObjectMethodV(class com/ke/infrastructrue/app/signature/util/MD5Utils, getMd5("sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580") => "108C29105573A8DE6A342C56FADC472A") was called from RX@0x120114e8[libkmsec.so]0x114e8
JNIEnv->GetStringUtfChars("108C29105573A8DE6A342C56FADC472A") was called from RX@0x12018030[libkmsec.so]0x18030
JNIEnv->FindClass(java/lang/String) was called from RX@0x12018108[libkmsec.so]0x18108
JNIEnv->NewStringUTF("") was called from RX@0x12018124[libkmsec.so]0x18124
JNIEnv->NewObjectArray(["", "", "", "", "", "", "", "", ""] => 9) was called from RX@0x12018140[libkmsec.so]0x18140
JNIEnv->NewStringUTF("C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513") was called from RX@0x12018158[libkmsec.so]0x18158
JNIEnv->SetObjectArrayElement(["", "", "", "", "", "", "", "", ""], 0) => "C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513" was called from RX@0x12018174[libkmsec.so]0x18174
JNIEnv->NewStringUTF("28B22DAC5AAD6BFBDBFD2DBB4F40E878") was called from RX@0x12018188[libkmsec.so]0x18188
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "", "", "", "", "", "", "", ""], 1) => "28B22DAC5AAD6BFBDBFD2DBB4F40E878" was called from RX@0x120181a4[libkmsec.so]0x181a4
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "", "", "", "", "", "", ""], 2) => "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14" was called from RX@0x120181c4[libkmsec.so]0x181c4
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "", "", "", "", "", ""], 3) => "sjoe98HI099dhdD7" was called from RX@0x120181e0[libkmsec.so]0x181e0
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "sjoe98HI099dhdD7", "", "", "", "", ""], 4) => "1782806580" was called from RX@0x12018200[libkmsec.so]0x18200
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "sjoe98HI099dhdD7", "1782806580", "", "", "", ""], 5) => "sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580" was called from RX@0x12018220[libkmsec.so]0x18220
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "sjoe98HI099dhdD7", "1782806580", "sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580", "", "", ""], 6) => "108C29105573A8DE6A342C56FADC472A" was called from RX@0x1201823c[libkmsec.so]0x1823c
JNIEnv->NewStringUTF("108C29105573A8DE6A342C56FADC472A") was called from RX@0x12018250[libkmsec.so]0x18250
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "sjoe98HI099dhdD7", "1782806580", "sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580", "108C29105573A8DE6A342C56FADC472A", "", ""], 7) => "108C29105573A8DE6A342C56FADC472A" was called from RX@0x1201826c[libkmsec.so]0x1826c
JNIEnv->NewStringUTF("C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513") was called from RX@0x12018280[libkmsec.so]0x18280
JNIEnv->SetObjectArrayElement(["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "sjoe98HI099dhdD7", "1782806580", "sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580", "108C29105573A8DE6A342C56FADC472A", "108C29105573A8DE6A342C56FADC472A", ""], 8) => "C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513" was called from RX@0x1201829c[libkmsec.so]0x1829c
[skip call site] 0x120182c8 -> 0x120182cc
JNIEnv->ReleaseStringUTFChars("sjoe98HI099dhdD7") was called from RX@0x120182ec[libkmsec.so]0x182ec
JNIEnv->ReleaseStringUTFChars("1782806580") was called from RX@0x12018304[libkmsec.so]0x18304
JNIEnv->ReleaseStringUTFChars("accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14") was called from RX@0x1201831c[libkmsec.so]0x1831c
["C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513", "28B22DAC5AAD6BFBDBFD2DBB4F40E878", "accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14", "sjoe98HI099dhdD7", "1782806580", "sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580", "108C29105573A8DE6A342C56FADC472A", "108C29105573A8DE6A342C56FADC472A", "C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513"]
SecManager.sign is called:
str=accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14,
str2=sjoe98HI099dhdD7,
str3=1782806580
SecManager.sign result=C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513,28B22DAC5AAD6BFBDBFD2DBB4F40E878,accessKeyId=sjoe98HI099dhdD7&appinfo-s=Beike;3.04.31;3043100&channel-s=Android_ke_vivo&device-id-s=35889edc8adee98f;DUBP09JwT0LhH4imjjwYtL96OwuMilL9S987RFVCUDA5SndUMExoSDRpbWpqd1l0TDk2T3d1TWlsTDlTOTg3c2h1;&hardware-s=google;Pixel 6&host=apps.api.ke.com&method=GET&nonce=TEQROKBgct3o6oBFVjglPtqldVOtNvdA&path=/xinfang/shellapp/search/index&query=city_id=510100&pre_request=1&request_ts=1782806580&signedHeaders=Device-id-s,AppInfo-s,User-Agent,Hardware-s,Channel-s,SystemInfo-s&systeminfo-s=android;14×tamp=1782806580&user-agent=Beike3.04.31;google Pixel+6; Android 14,sjoe98HI099dhdD7,1782806580,sjoe98HI099dhdD7uioefhli9847684328B22DAC5AAD6BFBDBFD2DBB4F40E8781782806580,108C29105573A8DE6A342C56FADC472A,108C29105573A8DE6A342C56FADC472A,C4D405F671C6859523BE9DC83E1F5C1DC3331724FC84E2D8073C572C339A3513

类型有一点不一样但是看值还是能看来结果是一样的至此unidbg分析完毕

3.IDA静态分析

3.1 定位函数与观察流程

首先去IDEA的日志输出中找到目标函数的注册地址,最后已经写的很明确了位置是0x17d00

这个伪C是我修改过后的,可读性提升了很多.我大概梳理一下他的一个执行流程.

该函数首先会解密 native 中隐藏的字符串并与参数2 arg2 转换后的 C 字符串进行比较,如果二者相等,则直接使用 native 内部预设的证书 MD5;如果不相等,则从 Java 层调用 SecManager.getCertificateMD5Digest() 获取当前安装包的证书 MD5,并在长度异常时使用兜底证书 MD5,随后在 arg1arg2arg3 都存在的情况下进入签名计算流程,先将 s 缓冲区拼接为 arg2 + "uioefhli98476843" + 证书MD5 + arg3,再调用 MD5Utils.getMd5(s) 获取 s 的 MD5 值,然后调用 hmac_sha256,其中参数1为上一步的 MD5 值,参数2为 MD5 值长度,参数3为 c_str_arg1,参数4为 c_str_arg1 长度,参数5为 hmac_sha256_result 输出缓冲区地址,参数6为输出长度 32,最后将 hmac_sha256_result 中的 32 字节结果逐字节按照 "%02X" 格式化成 64 位大写十六进制签名字符串,并按固定顺序放入 String[9] 对象数组中返回。

jobjectArray __fastcall sub_17D00(JNIEnv *env, __int64 a2, void *arg1, void *arg2, void *arg3)
{
  const char *c_str_arg2; // x23
  unsigned __int8 v10; // w8
  jclass SecManager; // x21
  struct _jmethodID *SecManager_getCertificateMD5Digest; // x0
  void *j_str_CertificateMD5_result; // x0
  const char *c_str_CertificateMD5_result; // x26
  long double v15; // q0
  jobjectArray result_Array; // x21
  unsigned __int8 v17; // w8
  const char *c_str_arg3; // x28
  const char *c_str_arg1; // x20
  jclass MD5Utils; // x21
  struct _jmethodID *MD5Utils_getMd5; // x24
  void *str_getMd5_result; // x27
  char *getMd5_result; // x24
  unsigned int v24; // w21
  unsigned int v25; // w0
  _WORD *v26; // x25
  __int64 i; // x20
  jclass v28; // x21
  jstring v29; // x0
  jstring v30; // x0
  jstring v31; // x0
  jstring v32; // x0
  jstring v33; // x0
  jstring java_str_s; // [xsp+18h] [xbp-118h]
  const char *str_arg1_1; // [xsp+20h] [xbp-110h]
  void *arg1_1; // [xsp+30h] [xbp-100h]
  void *arg3_1; // [xsp+38h] [xbp-F8h]
  char v39[4]; // [xsp+4Ch] [xbp-E4h] BYREF
  unsigned __int8 hmac_sha256_result[36]; // [xsp+50h] [xbp-E0h] BYREF
  char s[108]; // [xsp+74h] [xbp-BCh] BYREF

  _ReadStatusReg(TPIDR_EL0);
  c_str_arg2 = (*env)->GetStringUTFChars(env, arg2, 0);
  if ( !c_str_arg2 )
    return 0;
  v10 = atomic_load((unsigned __int8 *)&byte_49E48);
  if ( (v10 & 1) == 0 && __cxa_guard_acquire((__guard *)&byte_49E48) )
  {
    xmmword_49E30 = xmmword_30980;              // 某个隐藏字符串
    byte_49E40 = 46;
    __cxa_atexit((void (*)(void *))sub_10AC0, &xmmword_49E30, &unk_49000);
    __cxa_guard_release((__guard *)&byte_49E48);
  }
  sub_10E58(&xmmword_49E30);                    // 解密字符串
  if ( !strcmp(c_str_arg2, (const char *)&xmmword_49E30) )// 用参数2和解密字符串比较 5GILsthOHGOFRfuv
                                                // strcmp相等等于0取反是非0,所以相等就进if不等进else
                                                // 经过实测5GILsthOHGOFRfuv是开发环境用的key
  {
    v17 = atomic_load((unsigned __int8 *)&byte_49E78);
    if ( (v17 & 1) == 0 && __cxa_guard_acquire((__guard *)&byte_49E78) )
    {
      xmmword_49E50 = xmmword_30990;
      xmmword_49E60 = xmmword_309A0;
      byte_49E70 = 46;
      __cxa_atexit((void (*)(void *))sub_18418, &xmmword_49E50, &unk_49000);
      __cxa_guard_release((__guard *)&byte_49E78);
    }
    c_str_CertificateMD5_result = (const char *)&xmmword_49E50;
    *(int8x16_t *)&v15 = sub_18444((int8x16_t *)&xmmword_49E50);
  }
  else
  {                                             // 真正的流程走else
    SecManager = (*env)->FindClass(env, "com/ke/securitylib/SecManager");// 找到com/ke/securitylib/SecManager类
    SecManager_getCertificateMD5Digest = (*env)->GetStaticMethodID(// 通过com/ke/securitylib/SecManager找到类下的静态方法getCertificateMD5Digest的ID,方法无参返回String
                                           env,
                                           SecManager,
                                           "getCertificateMD5Digest",
                                           "()Ljava/lang/String;");
    j_str_CertificateMD5_result = (void *)call_static_method_v(// 调用方法
                                            env,
                                            SecManager,
                                            SecManager_getCertificateMD5Digest);
    c_str_CertificateMD5_result = (*env)->GetStringUTFChars(env, j_str_CertificateMD5_result, 0);// 把CertificateMD5_result的jstring转成c中的char *
    if ( (*env)->ExceptionCheck(env) )          // 检查JNI调用过程中有没有Java异常
      (*env)->ExceptionClear(env);              // 如果检测到异常,就把异常清掉
    (*env)->DeleteLocalRef(env, SecManager);    // 删除SecManager本地引用
    if ( strlen(c_str_CertificateMD5_result) != 32 )// 判断str_CertificateMD5_result的长度不是32
      c_str_CertificateMD5_result = (const char *)sub_11F7C();// 通过hook得知结果是28B22DAC5AAD6BFBDBFD2DBB4F40E878
  }
  c_str_arg3 = (const char *)((__int64 (__fastcall *)(JNIEnv *, void *, _QWORD, long double))(*env)->GetStringUTFChars)(
                               env,
                               arg3,
                               0,
                               v15);            // 把arg3的jstring转成c中的char *
  result_Array = 0;                             // 提前变量清空,避免它是野指针
  if ( arg1 && arg2 && arg3 )                   // 判断三个入参都不为 NULL
  {
    arg3_1 = arg3;                              // 保存arg3,后面继续使用
    arg1_1 = arg1;                              // 保存arg1,后面继续使用
    c_str_arg1 = (*env)->GetStringUTFChars(env, arg1, 0);// 把arg1的jstring转成c中的char *
    memset(s, 0, 0x64u);                        // 将s缓冲区前100字节全部清零
    sprintf(s, "%s%s%s%s", c_str_arg2, "uioefhli98476843", c_str_CertificateMD5_result, c_str_arg3);// 将 accessKey + 固定盐值 + 证书MD5 + arg3 拼接到 s 缓冲区中,作为后续签名/摘要计算的输入
    MD5Utils = (*env)->FindClass(env, "com/ke/infrastructrue/app/signature/util/MD5Utils");// 找到com/ke/infrastructrue/app/signature/util/MD5Utils类
    MD5Utils_getMd5 = (*env)->GetStaticMethodID(env, MD5Utils, "getMd5", "(Ljava/lang/String;)Ljava/lang/String;");// 通过com/ke/infrastructrue/app/signature/util/MD5Utils找到类下的静态方法getMd5的ID,方法有参String返回String
    java_str_s = (*env)->NewStringUTF(env, s);  // 将c的字符串s转成Java层的String对象
    str_getMd5_result = (void *)call_static_method_v(env, MD5Utils, MD5Utils_getMd5);// 调用getMd5方法,这里ida识别问题没有把java_str_s传进去,汇编中可以清晰看见是当作X3传进去了
    getMd5_result = (char *)((__int64 (__fastcall *)(JNIEnv *))(*env)->GetStringUTFChars)(env);// 把getMd5_result的jstring转成c中的char *
    if ( (*env)->ExceptionCheck(env) )          // 检查JNI调用过程中有没有Java异常
      (*env)->ExceptionClear(env);              // 如果检测到异常,就把异常清掉
    (*env)->DeleteLocalRef(env, MD5Utils);      // 删除MD5Utils本地引用
    memset(hmac_sha256_result, 0, 32);          // 准备 32 位缓冲区,准备给 hmac_sha256 接收结果
    v24 = strlen(getMd5_result);
    v25 = strlen(c_str_arg1);
    str_arg1_1 = c_str_arg1;
    hmac_sha256(getMd5_result, v24, (__int64)c_str_arg1, v25, hmac_sha256_result, 0x20u);
    v26 = malloc(0x41u);
    for ( i = 0; i != 32; ++i )
    {
      sprintf(v39, "%02X", hmac_sha256_result[i]);
      v26[i] = *(_WORD *)v39;
    }
    *((_BYTE *)v26 + 64) = 0;
    v28 = (*env)->FindClass(env, "java/lang/String");
    v29 = (*env)->NewStringUTF(env, "");
    result_Array = (*env)->NewObjectArray(env, 9, v28, v29);
    v30 = (*env)->NewStringUTF(env, v26);
    (*env)->SetObjectArrayElement(env, result_Array, 0, v30);
    v31 = (*env)->NewStringUTF(env, c_str_CertificateMD5_result);
    (*env)->SetObjectArrayElement(env, result_Array, 1, v31);
    (*env)->SetObjectArrayElement(env, result_Array, 2, arg1_1);
    (*env)->SetObjectArrayElement(env, result_Array, 3, arg2);
    (*env)->SetObjectArrayElement(env, result_Array, 4, arg3_1);
    (*env)->SetObjectArrayElement(env, result_Array, 5, java_str_s);
    (*env)->SetObjectArrayElement(env, result_Array, 6, str_getMd5_result);
    v32 = (*env)->NewStringUTF(env, getMd5_result);
    (*env)->SetObjectArrayElement(env, result_Array, 7, v32);
    v33 = (*env)->NewStringUTF(env, v26);
    (*env)->SetObjectArrayElement(env, result_Array, 8, v33);
    (*env)->DeleteLocalRef(env, java_str_s);
    (*env)->DeleteLocalRef(env, str_getMd5_result);
    free(getMd5_result);
    free(v26);
    (*env)->ReleaseStringUTFChars(env, arg2, c_str_arg2);
    (*env)->ReleaseStringUTFChars(env, arg3_1, c_str_arg3);
    (*env)->ReleaseStringUTFChars(env, arg1_1, str_arg1_1);
  }
  return result_Array;
}

.text:000000000001808C                 MOV             X3, X0  ; 第4个参数是 c_str_arg1的长度
.text:0000000000018090                 ADD             X4, SP, #0x130+hmac_sha256_result ; 第5个参数是 hmac_sha256_result 这块缓冲区的地址
.text:0000000000018094                 MOV             W5, #0x20 ; 第6个参数是 32
.text:0000000000018094                                         ; 下一行:第一个参数 X0 = char * getMd5 (因为前面BL过所以X0已经被改变)
.text:0000000000018098                 MOV             X0, X24 ; void *
.text:000000000001809C                 MOV             W1, W21 ; 第2个参数 getMd5 的长度
.text:00000000000180A0                 MOV             X2, X20 ; 第3个参数 c_str_arg1
.text:00000000000180A4                 ADD             X28, SP, #0x130+hmac_sha256_result ; 把栈上 hmac_sha256_result 这个 32 字节缓冲区的地址,再保存一份到 X28
.text:00000000000180A8                 STR             X20, [SP,#0x130+str_arg1_1] ; 把 X20 的值,保存到栈上的 str_arg1_1 这个位置 X20 = c_str_arg1
.text:00000000000180AC                 BL              .hmac_sha256 ; 调用 hmac_sha256
.text:00000000000180AC                                         ; 下一行:W0 = 65

3.2分析hmac_sha256

因为观察整个函数的一个流程可以看得出来最重要的地方是在hmac_sha256函数里面,因为他是唯一的一个在so中实现的加密,所以来深入hmac_sha256函数来看看他是怎么写的.

这个 hmac_sha256 函数先用 a1/a2 作为 key 和 key 长度初始化 HMAC 环境,然后把 a3/a4 这段数据喂进 inner 环境并通过 final 得到中间结果,再把中间结果喂进 outer 环境并通过 final 得到最终 HMAC-SHA256,最后把结果按 a6 长度复制到 a5 输出缓冲区。  

// 把 X28 保存到栈上
void *__fastcall hmac_sha256(void *a1, __int64 a2, __int64 a3, unsigned int a4, void *a5, unsigned int a6)
{
  _BYTE v11[168]; // [xsp+8h] [xbp-3A8h] BYREF
  _BYTE v12[632]; // [xsp+B0h] [xbp-300h] BYREF
  _BYTE v13[32]; // [xsp+328h] [xbp-88h] BYREF
  _BYTE v14[32]; // [xsp+348h] [xbp-68h] BYREF
  __int64 v15; // [xsp+368h] [xbp-48h]

  v15 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
  hmac_sha256_init((int)v11, a1);
  sha256_update(v11, a3, a4);
  sha256_final(v11, v14);
  sha256_update(v12, v14, 32);
  sha256_final(v12, v13);
  return memcpy(a5, v13, a6);
}

3.2.1 hmac_sha256_init
  1. 这个函数本质是 HMAC-SHA256 的初始化函数(hmac_sha256_init),其主要作用是为后续的 HMAC 计算构建初始状态。函数首先接收 ctxkeykey_len 作为输入参数,并对 key 进行标准化处理:当 key 长度超过 64 字节时,会先对其进行 SHA256 压缩为 32 字节;否则直接使用原始 key。随后将 key 规范化为 64 字节的标准密钥块,以符合 HMAC 的 block size 要求。
  2. 在此基础上,函数在 ctx 内部分别构造两块 64 字节缓冲区,用于生成 inner pad(ipad)和 outer pad(opad)。其中在前 key_len 范围内,分别执行 key[i] ^ 0x36 生成 ipad,以及 key[i] ^ 0x5C 生成 opad;剩余未覆盖的部分分别用常量 0x36 和 0x5C 进行填充,从而保证两个 pad 都严格为 64 字节。
  3. 在实现层面,函数对 pad 构造过程进行了优化:当数据较大时使用 16 字节 NEON 向量指令进行并行处理以提升性能,同时在写入过程中还加入了内存重叠检测,避免 key 与目标缓冲区发生覆盖导致数据错误。
  4. 完成 pad 构造后,函数分别调用 sha256_initsha256_update 对 inner pad 和 outer pad 初始化 SHA256 状态,得到 inner state 与 outer state,并将这两套状态保存到 ctx 内部作为备份,以便后续 HMAC 计算时可以直接复用该初始状态,无需重复构造 key 相关数据。
  5. 最后,函数还包含 stack canary 校验机制,用于检测栈是否被破坏,从而增强函数的安全性。
.text:00000000000179B0 ; __unwind {
.text:00000000000179B0                 SUB             SP, SP, #0x70
.text:00000000000179B4                 STP             X24, X23, [SP,#0x60+var_30] ; 把 X24 和 X23 保存到栈上
.text:00000000000179B8                 STP             X22, X21, [SP,#0x60+var_20] ; 把 X22 和 X21 保存到栈上
.text:00000000000179BC                 STP             X20, X19, [SP,#0x60+var_10] ; 把 X20 和 X19 保存到栈上
.text:00000000000179C0                 STP             X29, X30, [SP,#0x60+var_s0] ; 保存旧的帧指针和返回地址
.text:00000000000179C4                 ADD             X29, SP, #0x60 ; 所以新的 FP 指向保存旧 FP/LR 的位置
.text:00000000000179C8                 MRS             X23, TPIDR_EL0 ; 当前线程 TLS 基地址
.text:00000000000179CC                 LDR             X8, [X23,#0x28] ; 读取当前线程的 stack canary
.text:00000000000179D0                 MOV             W21, W2 ; W21 = key_len
.text:00000000000179D4                 MOV             X20, X1 ; X20 = key
.text:00000000000179D8                 MOV             X19, X0 ; X19 = &ctx
.text:00000000000179DC                 CMP             W21, #0x40 ; '@' ; W21 = key_len 和 64比较
.text:00000000000179E0                 STR             X8, [SP,#0x60+var_38] ; 把当前线程的 stack canary 保存到栈上
.text:00000000000179E4                 B.EQ            loc_17A3C ; 如果 key_len == 64,跳到 loc_17A3C,直接处理 key ^ ipad/opad
.text:00000000000179E8                 CMP             W21, #0x41 ; 'A' ; W21 = key_len 和 65比较
.text:00000000000179EC                 B.CC            loc_17A08 ; 如果 key_len < 65 跳转;由于 key_len==64 前面已跳走,所以这里主要是 key_len<64,后面会补齐 ipad/opad 剩余部分
.text:00000000000179F0                 ADD             X2, SP, #0x60+var_58 ; 参数3 = key_hash 临时缓冲区,用来接收 sha256(key) 的输出(如果走到了这里意味着key的长度大于64就需要先对key进行一次sha256压缩到32字节后续再进行填充)
.text:00000000000179F0                                         ; 参数1 = key
.text:00000000000179F4                 MOV             X0, X20 ; void *
.text:00000000000179F8                 MOV             W1, W21 ; 参数2 = key_len
.text:00000000000179FC                 BL              .sha256 ; 执行函数
.text:0000000000017A00                 MOV             W21, #0x20 ; ' ' ; W21 = 32
.text:0000000000017A04                 ADD             X20, SP, #0x60+var_58 ; X20 = key_hash,把后续使用的 key 指针改成这个临时 hash 结果
.text:0000000000017A08
.text:0000000000017A08 loc_17A08                               ; CODE XREF: hmac_sha256_init+3C↑j
.text:0000000000017A08                 MOV             W8, #0x40 ; '@' ; W8 = 64
.text:0000000000017A0C                 ADD             X24, X19, W21,UXTW ; X24 = &ctx+key_len
.text:0000000000017A10                 SUB             W22, W8, W21 ; W22 = 64 - ken_len 获取要补齐的key的长度
.text:0000000000017A10                                         ; 参数1 =
.text:0000000000017A10                                         ; 参数2 = 54
.text:0000000000017A10                                         ; 参数3 = 填充长度
.text:0000000000017A14                 ADD             X0, X24, #0x2A0 ; void *
.text:0000000000017A18                 MOV             W1, #0x36 ; '6' ; int
.text:0000000000017A1C                 MOV             X2, X22 ; size_t
.text:0000000000017A20                 BL              .memset ; 把 inner pad 剩余部分填成 0x36
.text:0000000000017A20                                         ; 参数1 = ctx + key_len + 0x2E0 准备给 opad 的剩余部分填充
.text:0000000000017A20                                         ; 参数2 = 0x5C
.text:0000000000017A20                                         ; 参数3 = 64 - key_len
.text:0000000000017A24                 ADD             X0, X24, #0x2E0 ; void *
.text:0000000000017A28                 MOV             W1, #0x5C ; '\' ; int
.text:0000000000017A2C                 MOV             X2, X22 ; size_t
.text:0000000000017A30                 BL              .memset ; 把 outer pad 剩余部分填成 0x5C
.text:0000000000017A34                 CMP             W21, #1 ; 判断key_len和1
.text:0000000000017A38                 B.LT            loc_17AB0 ; 如果key_len小于1那就意味着key的ipad和opad都已经填充完成了,就可以跳转到这个标签去了
.text:0000000000017A3C
.text:0000000000017A3C loc_17A3C                               ; CODE XREF: hmac_sha256_init+34↑j
.text:0000000000017A3C                 CMP             W21, #0x10 ; 判断key_len和16
.text:0000000000017A40                 MOV             W8, W21 ; W8 = key_len
.text:0000000000017A44                 B.CC            loc_17A74 ; 如果ken_len小于16跳转到这个标签
.text:0000000000017A48                 AND             W10, W8, #0xF ; W10 = key_len % 16
.text:0000000000017A4C                 SUB             X9, X8, X10 ; X9 = key_len - key_len % 16 (能用 16 字节一组处理的长度)
.text:0000000000017A50                 CBZ             X9, loc_17A78 ; 如果X9=0那就如果没有任何完整的 16 字节块,就跳到普通字节处理
.text:0000000000017A54                 ADD             X11, X19, #0x2A0 ; X11=&ctx+0x2A0(ipad缓冲区地址)
.text:0000000000017A58                 ADD             X12, X20, X8 ; X12=key+key_len 也就是 key 数据的结束地址
.text:0000000000017A5C                 CMP             X11, X12 ; 比较X11 = ipad 起始地址 = ctx + 0x2A0 和 X12 = key 结束地址 = key + key_len
.text:0000000000017A60                 B.CS            loc_17B2C ; 如果 ipad_start >= key_end,说明 ipad 区域在 key 后面,不会和 key 源数据重叠
.text:0000000000017A64                 ADD             X11, X19, X8 ; X11 = ctx + key_len
.text:0000000000017A68                 ADD             X11, X11, #0x2E0 ; X11 = X11 + 0X2E0 opad 的结束位置
.text:0000000000017A6C                 CMP             X20, X11 ; 判断X20 和 X11
.text:0000000000017A70                 B.CS            loc_17B2C ; 如果key_len >= opad_end 说明 key 区域在 opad 区域后面,也说明两块内存不重叠
.text:0000000000017A74
.text:0000000000017A74 loc_17A74                               ; CODE XREF: hmac_sha256_init+94↑j
.text:0000000000017A74                 MOV             X9, XZR ; X9=0
.text:0000000000017A78
.text:0000000000017A78 loc_17A78                               ; CODE XREF: hmac_sha256_init+A0↑j
.text:0000000000017A78                                         ; hmac_sha256_init+1B0↓j
.text:0000000000017A78                 ADD             X11, X19, X9 ; X11 = ctx + i
.text:0000000000017A7C                 ADD             X10, X20, X9 ; X10=&X20(key)[X9(i)]
.text:0000000000017A80                 SUB             X8, X8, X9 ; X8=X8(key_len)-X9(已经处理的字节数量)
.text:0000000000017A84                 MOV             W9, #0x36 ; '6' ; W9=0x36 ipad异或常量
.text:0000000000017A88                 ADD             X11, X11, #0x2E0 ; X11 = ctx + 0x2E0 + i = &opad[i]
.text:0000000000017A8C                 MOV             W12, #0x5C ; '\' ; W12=0x5C opad异或常量
.text:0000000000017A90
.text:0000000000017A90 loc_17A90                               ; CODE XREF: hmac_sha256_init+FC↓j
.text:0000000000017A90                 LDRB            W13, [X10] ; 读取当前 key 字节
.text:0000000000017A94                 SUB             X8, X8, #1 ; X8 = 剩余需要逐字节处理的长度 因为当前循环马上要处理一个字节,所以剩余数量先减少一次
.text:0000000000017A98                 EOR             W13, W13, W9 ; W13 = key[i] ^ 0x36;
.text:0000000000017A9C                 STURB           W13, [X11,#-0x40] ; ipad[i] = key[i] ^ 0x36;
.text:0000000000017AA0                 LDRB            W13, [X10],#1 ; 再次读取key的一个字节,然后X10++
.text:0000000000017AA4                 EOR             W13, W13, W12 ; W13 = key[i] ^ 0x5C;
.text:0000000000017AA8                 STRB            W13, [X11],#1 ; opad[i] = key[i] ^ 0x5C; X11++
.text:0000000000017AAC                 CBNZ            X8, loc_17A90 ; 如果还有剩余的字节没有处理就进入循环
.text:0000000000017AB0
.text:0000000000017AB0 loc_17AB0                               ; CODE XREF: hmac_sha256_init+88↑j
.text:0000000000017AB0                                         ; hmac_sha256_init+1B4↓j
.text:0000000000017AB0                 MOV             X0, X19 ; 参数1=&ctx->inner;
.text:0000000000017AB4                 BL              .sha256_init ; 调用 sha256_init 参数1为&ctx
.text:0000000000017AB8                 ADD             X1, X19, #0x2A0 ; 参数2 = &ctx->ipad[0]
.text:0000000000017ABC                 MOV             W2, #0x40 ; '@' ; 参数3 = 64 长度
.text:0000000000017AC0                 MOV             X0, X19 ; 参数1 = &ctx->inner;
.text:0000000000017AC4                 BL              .sha256_update ; 调用sha256_update函数
.text:0000000000017AC8                 ADD             X20, X19, #0xA8 ; X20=&ctx->outer
.text:0000000000017ACC                 MOV             X0, X20 ; 参数1=&ctx->outer
.text:0000000000017AD0                 BL              .sha256_init ; sha256_init(&ctx->outer);
.text:0000000000017AD4                 ADD             X1, X19, #0x2E0 ; X1 = &ctx->opad[0];
.text:0000000000017AD8                 MOV             W2, #0x40 ; '@' ; W2=64
.text:0000000000017ADC                 MOV             X0, X20 ; X0=&ctx->outer
.text:0000000000017AE0                 BL              .sha256_update ; sha256_update(&ctx->outer, key ^ opad, 64);
.text:0000000000017AE4                 ADD             X0, X19, #0x150 ; void *
.text:0000000000017AE8                 MOV             W2, #0xA8 ; size_t
.text:0000000000017AEC                 MOV             X1, X19 ; void *
.text:0000000000017AF0                 BL              .memcpy ; memcpy(&ctx->inner_backup, &ctx->inner, 0xA8);
.text:0000000000017AF4                 ADD             X0, X19, #0x1F8 ; void *
.text:0000000000017AF8                 MOV             W2, #0xA8 ; size_t
.text:0000000000017AFC                 MOV             X1, X20 ; void *
.text:0000000000017B00                 BL              .memcpy ; memcpy(&ctx->outer_backup, &ctx->outer, 0xA8);
.text:0000000000017B04                 LDR             X8, [X23,#0x28] ; 重新读取当前线程 TLS 里的 stack canary
.text:0000000000017B08                 LDR             X9, [SP,#0x60+var_38] ; 读取保存到栈上的 stack canary
.text:0000000000017B0C                 CMP             X8, X9  ; 比较两个 stack canary
.text:0000000000017B10                 B.NE            loc_17B68 ; 如果 canary 不一样,就说明栈可能被破坏了,程序会进入栈保护失败处理
.text:0000000000017B14                 LDP             X29, X30, [SP,#0x60+var_s0] ; 恢复旧FP和旧LR
.text:0000000000017B18                 LDP             X20, X19, [SP,#0x60+var_10] ; 恢复寄存器
.text:0000000000017B1C                 LDP             X22, X21, [SP,#0x60+var_20] ; 恢复寄存器
.text:0000000000017B20                 LDP             X24, X23, [SP,#0x60+var_30] ; 恢复寄存器
.text:0000000000017B24                 ADD             SP, SP, #0x70 ; 'p' ; 释放当前函数栈帧空间
.text:0000000000017B28                 RET
.text:0000000000017B2C ; ---------------------------------------------------------------------------
.text:0000000000017B2C
.text:0000000000017B2C loc_17B2C                               ; CODE XREF: hmac_sha256_init+B0↑j
.text:0000000000017B2C                                         ; hmac_sha256_init+C0↑j
.text:0000000000017B2C                 ADD             X11, X19, #0x2E0 ; X11 = &ctx->opad[0];
.text:0000000000017B30                 MOVI            V0.16B, #0x36 ; '6' ; 给V0向量寄存器填充16个立即数0x36
.text:0000000000017B34                 MOVI            V1.16B, #0x5C ; '\' ; 给V1向量寄存器填充16个立即数0x5C
.text:0000000000017B38                 MOV             X12, X9 ; X12=能用 16 字节一组处理的长度
.text:0000000000017B3C                 MOV             X13, X20 ; X13 = key
.text:0000000000017B40
.text:0000000000017B40 loc_17B40                               ; CODE XREF: hmac_sha256_init+1AC↓j
.text:0000000000017B40                 LDR             Q2, [X13] ; Q2 = key[i : i+16]
.text:0000000000017B44                 SUB             X12, X12, #0x10 ; X12 -= 16; 因为当前循环马上处理 16 字节,所以剩余向量处理长度减 16
.text:0000000000017B48                 EOR             V2.16B, V2.16B, V0.16B ; ipad[i : i+16] = key[i : i+16] ^ 0x36
.text:0000000000017B4C                 STUR            Q2, [X11,#-0x40] ; ipad[i : i+16] = key[i : i+16] ^ 0x36;
.text:0000000000017B50                 LDR             Q2, [X13],#0x10 ; 再次读取同一组 16 字节 key,然后 X13 后移 16 字节,指向下一组 key 因为前面的 Q2 已经被异或成 key ^ 0x36 了。现在还要生成 key ^ 0x5C,所以重新读取原始 key 的 16 字节
.text:0000000000017B54                 EOR             V2.16B, V2.16B, V1.16B ; opad[i : i+16] = key[i : i+16] ^ 0x5C;
.text:0000000000017B58                 STR             Q2, [X11],#0x10 ; opad[i : i+16] = key[i : i+16] ^ 0x5C;
.text:0000000000017B5C                 CBNZ            X12, loc_17B40 ; 如果还有完整的 16 字节块没处理,就继续循环。
.text:0000000000017B60                 CBNZ            W10, loc_17A78 ; 如果 W10 != 0,说明 key_len 不是 16 的整数倍,还有剩余几个字节没有处理 所以跳到 loc_17A78,用普通逐字节循环处理最后 5 个字节
.text:0000000000017B64                 B               loc_17AB0 ; 如果 W10 == 0,说明没有尾巴,所有 key 字节都处理完了
.text:0000000000017B68 ; ---------------------------------------------------------------------------
.text:0000000000017B68
.text:0000000000017B68 loc_17B68                               ; CODE XREF: hmac_sha256_init+160↑j
.text:0000000000017B68                 BL              .__stack_chk_fail ; 开始栈保护失败处理
.text:0000000000017B68 ; } // starts at 179B0
.text:0000000000017B68 ; End of function hmac_sha256_init
3.2.2 sha256_update
  1. 这个函数本质是 SHA256 的 update 阶段实现,也就是 **sha256_update**。它的主要作用不是直接输出 hash,而是负责接收任意长度的输入数据,并把这些数据按照 SHA256 的规则整理成一个个 64 字节 block,然后交给 sha256_transf 去做真正的压缩计算。
  2. 函数开始时,会先从 ctx 中读取当前 buffer 里已经缓存了多少数据,也就是 buffer_len。因为 SHA256 每次只能处理 64 字节,所以如果上一次输入的数据没有凑满 64 字节,就会被暂存在 ctx->buffer 中。当前这次 update 进来后,函数会先计算这个 buffer 还剩多少空间可以填充。
  3. 接着,函数会先用 memcpy 把新输入数据的一部分复制到 ctx->buffer 后面,尝试把之前没满的 buffer 补齐。如果补完之后还是不足 64 字节,说明当前还不能触发 SHA256 压缩,于是函数只会更新 ctx 里的 buffer_len,然后直接返回,等待下一次 update 继续传入数据。
  4. 如果补完之后已经达到或超过 64 字节,函数就会先把当前 ctx->buffer 当作一个完整的 64 字节 block,调用 sha256_transf(ctx, buffer, 1) 执行一次压缩。这个时候,之前缓存的数据加上本次输入的数据,就真正参与了 SHA256 状态更新。
  5. 处理完这个补齐出来的 block 后,函数会继续看剩余的新输入数据里还有多少个完整的 64 字节 block。这里通过 len >> 6,也就是除以 64,计算可以直接处理多少个完整 block,然后继续调用 sha256_transf 对这些完整 block 进行压缩处理。
  6. 当所有完整的 64 字节 block 都处理完后,如果最后还剩下一些不足 64 字节的数据,函数不会丢掉它们,而是再次用 memcpy 把这些尾部数据保存回 ctx->buffer。这些数据会留到下一次 sha256_update,或者最后的 sha256_final 阶段继续处理。
  7. 最后,函数会更新 ctx 里的长度信息,包括已经处理过的总字节数,以及当前 buffer 中剩余的尾部数据长度。这样后续 update 或 final 才知道前面已经处理了多少数据、当前还缓存了多少数据。
  8. 所以整体来说,这个函数就是 SHA256 的数据调度函数:它不断接收输入数据,把数据先拼到内部 buffer 里,凑满 64 字节就交给 **sha256_transf** 压缩,没凑满的尾部数据就继续保存起来,等待后续继续处理。
.text:000000000001695C ; __unwind {
.text:000000000001695C                 STR             X23, [SP,#-0x10+var_30]!
.text:0000000000016960                 STP             X22, X21, [SP,#0x30+var_20]
.text:0000000000016964                 STP             X20, X19, [SP,#0x30+var_10]
.text:0000000000016968                 STP             X29, X30, [SP,#0x30+var_s0]
.text:000000000001696C                 ADD             X29, SP, #0x30
.text:0000000000016970                 MOV             X19, X0
.text:0000000000016974                 LDR             W23, [X19,#4]
.text:0000000000016978                 MOV             W8, #0x40 ; '@'
.text:000000000001697C                 MOV             W21, W2
.text:0000000000016980                 MOV             X20, X1
.text:0000000000016984                 SUB             W8, W8, W23
.text:0000000000016988                 CMP             W8, W21
.text:000000000001698C                 ADD             X9, X19, X23
.text:0000000000016990                 CSEL            W22, W21, W8, HI
.text:0000000000016994                 ADD             X0, X9, #8 ; void *
.text:0000000000016998                 MOV             X2, X22 ; size_t
.text:000000000001699C                 BL              .memcpy
.text:00000000000169A0                 ADD             W8, W23, W21
.text:00000000000169A4                 CMP             W8, #0x3F ; '?'
.text:00000000000169A8                 B.HI            loc_169B4
.text:00000000000169AC                 STR             W8, [X19,#4]
.text:00000000000169B0                 B               loc_16A10
.text:00000000000169B4 ; ---------------------------------------------------------------------------
.text:00000000000169B4
.text:00000000000169B4 loc_169B4                               ; CODE XREF: sha256_update+4C↑j
.text:00000000000169B4                 SUB             W23, W21, W22
.text:00000000000169B8                 ADD             X20, X20, X22
.text:00000000000169BC                 ADD             X22, X19, #8
.text:00000000000169C0                 MOV             W2, #1
.text:00000000000169C4                 MOV             X0, X19
.text:00000000000169C8                 MOV             X1, X22
.text:00000000000169CC                 LSR             W21, W23, #6
.text:00000000000169D0                 BL              .sha256_transf
.text:00000000000169D4                 MOV             X0, X19
.text:00000000000169D8                 MOV             X1, X20
.text:00000000000169DC                 MOV             W2, W21
.text:00000000000169E0                 BL              .sha256_transf
.text:00000000000169E4                 AND             W21, W23, #0x3F ; '?'
.text:00000000000169E8                 AND             W8, W23, #0xFFFFFFC0
.text:00000000000169EC                 ADD             X1, X20, X8 ; void *
.text:00000000000169F0                 MOV             X0, X22 ; void *
.text:00000000000169F4                 MOV             X2, X21 ; size_t
.text:00000000000169F8                 BL              .memcpy
.text:00000000000169FC                 LDR             W8, [X19]
.text:0000000000016A00                 ADD             W9, W23, #0x40 ; '@'
.text:0000000000016A04                 AND             W9, W9, #0xFFFFFFC0
.text:0000000000016A08                 ADD             W8, W8, W9
.text:0000000000016A0C                 STP             W8, W21, [X19]
.text:0000000000016A10
.text:0000000000016A10 loc_16A10                               ; CODE XREF: sha256_update+54↑j
.text:0000000000016A10                 LDP             X29, X30, [SP,#0x30+var_s0]
.text:0000000000016A14                 LDP             X20, X19, [SP,#0x30+var_10]
.text:0000000000016A18                 LDP             X22, X21, [SP,#0x30+var_20]
.text:0000000000016A1C                 LDR             X23, [SP+0x30+var_30],#0x40
.text:0000000000016A20                 RET
.text:0000000000016A20 ; } // starts at 1695C
.text:0000000000016A20 ; End of function sha256_update
3.2.3 sha256_final
  1. 这个函数是 SHA256 的 final 结束函数 **sha256_final**,它负责在所有数据通过 sha256_update 输入完成后,执行最后的 padding、最终压缩,并把 SHA256 的最终结果输出到 out 缓冲区中。
  2. 函数开始后,首先从 ctx 中读取两个关键值:一个是前面已经处理过的数据长度,另一个是当前 buffer 中还剩余、但尚未凑满 64 字节的数据长度。也就是说,sha256_update 已经把能处理的完整 64 字节 block 都处理掉了,而这个函数要处理的是最后剩下的尾部数据。
  3. 接下来函数会按照 SHA256 标准规则给最后的数据做 padding。它会先在当前 buffer 的末尾追加一个 0x80 字节,这个字节表示消息正文结束;然后在后面继续填充 0x00,直到剩余空间能够放下消息长度字段。如果当前 64 字节 block 剩余空间不够保存长度字段,函数就会额外再构造一个新的 64 字节 block,所以这里最终可能会处理 1 个 block,也可能会处理 2 个 block。
  4. 然后函数会计算消息总长度。注意这里保存的不是“字节长度”,而是“bit 长度”,所以代码里会把总字节数左移 3 位,也就是乘以 8。随后它会把这个 bit 长度按照大端格式写入 padding 区域的末尾。严格来说,SHA256 标准最后预留的是 8 字节长度字段;这段代码因为只处理 32 位长度,所以高 4 字节通过前面的 memset 保持为 0,低 4 字节写入真实的 bit 长度。
  5. padding 和长度字段都准备好后,函数会调用 sha256_transf(ctx, buffer, block_count),对最后补齐出来的 1 个或 2 个 64 字节 block 执行最终压缩。这个压缩完成后,SHA256 的 8 个内部状态值 H0 ~ H7 就已经变成最终 hash 状态。
  6. 最后,函数从 ctx + 0x88 开始依次读取这 8 个 32 位状态值,也就是 H0 ~ H7,然后把每个 32 位整数拆成 4 个字节,并按照大端字节序写入输出缓冲区 out。因为 SHA256 一共有 8 个状态值,每个状态值 4 字节,所以最终输出就是标准的 32 字节 SHA256 摘要。
  7. 所以整体来说,这个函数就是 SHA256 的收尾函数:它负责给最后不足 64 字节的数据补 **0x80**、补 **0x00**、写入消息 bit 长度,再调用压缩函数处理最后的 block,最后把 **ctx** 中的 8 个 hash 状态按大端格式输出成 32 字节结果。
.text:0000000000016A24 ; __unwind {
.text:0000000000016A24                 STP             X26, X25, [SP,#-0x10+var_40]!
.text:0000000000016A28                 STP             X24, X23, [SP,#0x40+var_30]
.text:0000000000016A2C                 STP             X22, X21, [SP,#0x40+var_20]
.text:0000000000016A30                 STP             X20, X19, [SP,#0x40+var_10]
.text:0000000000016A34                 STP             X29, X30, [SP,#0x40+var_s0]
.text:0000000000016A38                 ADD             X29, SP, #0x40
.text:0000000000016A3C                 MOV             X20, X0
.text:0000000000016A40                 LDP             W9, W8, [X20]
.text:0000000000016A44                 MOV             W10, #1
.text:0000000000016A48                 ADD             X21, X20, #8
.text:0000000000016A4C                 MOV             X19, X1
.text:0000000000016A50                 AND             W11, W8, #0x38 ; '8'
.text:0000000000016A54                 CMP             W11, #0x37 ; '7'
.text:0000000000016A58                 CINC            W22, W10, HI
.text:0000000000016A5C                 LSL             W26, W22, #6
.text:0000000000016A60                 ADD             X23, X21, X8
.text:0000000000016A64                 ADD             W24, W9, W8
.text:0000000000016A68                 SUB             W2, W26, W8 ; size_t
.text:0000000000016A6C                 MOV             X0, X23 ; void *
.text:0000000000016A70                 MOV             W1, WZR ; int
.text:0000000000016A74                 LSL             W25, W24, #3
.text:0000000000016A78                 BL              .memset
.text:0000000000016A7C                 MOV             W8, #0x80
.text:0000000000016A80                 ADD             X9, X21, X26
.text:0000000000016A84                 LSR             W10, W24, #5
.text:0000000000016A88                 LSR             W11, W24, #0xD
.text:0000000000016A8C                 LSR             W12, W24, #0x15
.text:0000000000016A90                 MOV             X0, X20
.text:0000000000016A94                 MOV             X1, X21
.text:0000000000016A98                 MOV             W2, W22
.text:0000000000016A9C                 STRB            W8, [X23]
.text:0000000000016AA0                 STURB           W25, [X9,#-1]
.text:0000000000016AA4                 STURB           W10, [X9,#-2]
.text:0000000000016AA8                 STURB           W11, [X9,#-3]
.text:0000000000016AAC                 STURB           W12, [X9,#-4]
.text:0000000000016AB0                 BL              .sha256_transf
.text:0000000000016AB4                 LDR             W8, [X20,#0x88]
.text:0000000000016AB8                 STRB            W8, [X19,#3]
.text:0000000000016ABC                 LDR             W8, [X20,#0x88]
.text:0000000000016AC0                 LSR             W8, W8, #8
.text:0000000000016AC4                 STRB            W8, [X19,#2]
.text:0000000000016AC8                 LDRH            W8, [X20,#0x8A]
.text:0000000000016ACC                 STRB            W8, [X19,#1]
.text:0000000000016AD0                 LDRB            W8, [X20,#0x8B]
.text:0000000000016AD4                 STRB            W8, [X19]
.text:0000000000016AD8                 LDR             W8, [X20,#0x8C]
.text:0000000000016ADC                 STRB            W8, [X19,#7]
.text:0000000000016AE0                 LDR             W8, [X20,#0x8C]
.text:0000000000016AE4                 LSR             W8, W8, #8
.text:0000000000016AE8                 STRB            W8, [X19,#6]
.text:0000000000016AEC                 LDRH            W8, [X20,#0x8E]
.text:0000000000016AF0                 STRB            W8, [X19,#5]
.text:0000000000016AF4                 LDRB            W8, [X20,#0x8F]
.text:0000000000016AF8                 STRB            W8, [X19,#4]
.text:0000000000016AFC                 LDR             W8, [X20,#0x90]
.text:0000000000016B00                 STRB            W8, [X19,#0xB]
.text:0000000000016B04                 LDR             W8, [X20,#0x90]
.text:0000000000016B08                 LSR             W8, W8, #8
.text:0000000000016B0C                 STRB            W8, [X19,#0xA]
.text:0000000000016B10                 LDRH            W8, [X20,#0x92]
.text:0000000000016B14                 STRB            W8, [X19,#9]
.text:0000000000016B18                 LDRB            W8, [X20,#0x93]
.text:0000000000016B1C                 STRB            W8, [X19,#8]
.text:0000000000016B20                 LDR             W8, [X20,#0x94]
.text:0000000000016B24                 STRB            W8, [X19,#0xF]
.text:0000000000016B28                 LDR             W8, [X20,#0x94]
.text:0000000000016B2C                 LSR             W8, W8, #8
.text:0000000000016B30                 STRB            W8, [X19,#0xE]
.text:0000000000016B34                 LDRH            W8, [X20,#0x96]
.text:0000000000016B38                 STRB            W8, [X19,#0xD]
.text:0000000000016B3C                 LDRB            W8, [X20,#0x97]
.text:0000000000016B40                 STRB            W8, [X19,#0xC]
.text:0000000000016B44                 LDR             W8, [X20,#0x98]
.text:0000000000016B48                 STRB            W8, [X19,#0x13]
.text:0000000000016B4C                 LDR             W8, [X20,#0x98]
.text:0000000000016B50                 LSR             W8, W8, #8
.text:0000000000016B54                 STRB            W8, [X19,#0x12]
.text:0000000000016B58                 LDRH            W8, [X20,#0x9A]
.text:0000000000016B5C                 STRB            W8, [X19,#0x11]
.text:0000000000016B60                 LDRB            W8, [X20,#0x9B]
.text:0000000000016B64                 STRB            W8, [X19,#0x10]
.text:0000000000016B68                 LDR             W8, [X20,#0x9C]
.text:0000000000016B6C                 STRB            W8, [X19,#0x17]
.text:0000000000016B70                 LDR             W8, [X20,#0x9C]
.text:0000000000016B74                 LSR             W8, W8, #8
.text:0000000000016B78                 STRB            W8, [X19,#0x16]
.text:0000000000016B7C                 LDRH            W8, [X20,#0x9E]
.text:0000000000016B80                 STRB            W8, [X19,#0x15]
.text:0000000000016B84                 LDRB            W8, [X20,#0x9F]
.text:0000000000016B88                 STRB            W8, [X19,#0x14]
.text:0000000000016B8C                 LDR             W8, [X20,#0xA0]
.text:0000000000016B90                 STRB            W8, [X19,#0x1B]
.text:0000000000016B94                 LDR             W8, [X20,#0xA0]
.text:0000000000016B98                 LSR             W8, W8, #8
.text:0000000000016B9C                 STRB            W8, [X19,#0x1A]
.text:0000000000016BA0                 LDRH            W8, [X20,#0xA2]
.text:0000000000016BA4                 STRB            W8, [X19,#0x19]
.text:0000000000016BA8                 LDRB            W8, [X20,#0xA3]
.text:0000000000016BAC                 STRB            W8, [X19,#0x18]
.text:0000000000016BB0                 LDR             W8, [X20,#0xA4]
.text:0000000000016BB4                 STRB            W8, [X19,#0x1F]
.text:0000000000016BB8                 LDR             W8, [X20,#0xA4]
.text:0000000000016BBC                 LSR             W8, W8, #8
.text:0000000000016BC0                 STRB            W8, [X19,#0x1E]
.text:0000000000016BC4                 LDRH            W8, [X20,#0xA6]
.text:0000000000016BC8                 STRB            W8, [X19,#0x1D]
.text:0000000000016BCC                 LDRB            W8, [X20,#0xA7]
.text:0000000000016BD0                 STRB            W8, [X19,#0x1C]
.text:0000000000016BD4                 LDP             X29, X30, [SP,#0x40+var_s0]
.text:0000000000016BD8                 LDP             X20, X19, [SP,#0x40+var_10]
.text:0000000000016BDC                 LDP             X22, X21, [SP,#0x40+var_20]
.text:0000000000016BE0                 LDP             X24, X23, [SP,#0x40+var_30]
.text:0000000000016BE4                 LDP             X26, X25, [SP+0x40+var_40],#0x50
.text:0000000000016BE8                 RET
.text:0000000000016BE8 ; } // starts at 16A24
.text:0000000000016BE8 ; End of function sha256_final
3.2.4 sha256_transf
  1. 这个函数是 SHA256 的核心压缩函数 **sha256_transf**,它是整个 SHA256 算法里真正执行哈希运算的地方。前面的 sha256_update 主要负责接收数据、缓存数据、把数据凑成 64 字节块;而这个函数负责把每一个 64 字节 block 送进 SHA256 的 64 轮压缩计算中,最终更新 ctx 里的 hash 状态。
  2. 函数的输入参数主要有三个:X0ctx,也就是 SHA256 上下文;X1 是待处理的数据地址;W2block_count,表示这次要处理多少个 64 字节数据块。如果 block_count < 1,函数就不会进行压缩计算,直接跳到结尾返回。
  3. 进入正式处理后,函数会每次取出一个 64 字节 block。它先把这个 block 中的前 64 字节数据拆成 16 个 32 位整数,也就是 W[0] ~ W[15]。由于 SHA256 内部要求使用大端字节序,所以这里会通过 REV 指令把读取出来的 32 位数据进行字节序反转,然后保存到栈上的消息数组 W 中。
  4. 接着,函数会根据 SHA256 的消息扩展规则,把原来的 16 个 word 扩展成 64 个 word,也就是生成 W[16] ~ W[63]。这部分对应的公式大概是:W[i] = W[i-16] + σ0(W[i-15]) + W[i-7] + σ1(W[i-2])。汇编里大量的 RORLSREORADD,就是在计算这些小 sigma 函数和扩展结果。
  5. 消息扩展完成后,函数会从 ctx + 0x88 开始读取当前 SHA256 的 8 个状态值,也就是 h0 ~ h7,并把它们放入工作变量 a,b,c,d,e,f,g,h 中。后面的 64 轮压缩运算,就是围绕这 8 个工作变量进行不断更新。
  6. 在每一轮压缩中,函数都会读取一个常量 K[i] 和一个消息字 W[i],然后计算 SHA256 标准里的几个核心函数:Σ1(e)Ch(e,f,g)Σ0(a)Maj(a,b,c),再组合出临时值 T1T2。这些计算完成后,会更新 a,b,c,d,e,f,g,h 这 8 个工作变量,循环总共执行 64 轮。
  7. 当一个 block 的 64 轮计算结束后,函数不会直接丢弃这些工作变量,而是把最终的 a,b,c,d,e,f,g,h 分别加回原来的 ctx->h[0] ~ ctx->h[7]。这一步就是 SHA256 状态更新的关键:当前这个 64 字节 block 的内容,会通过这一步真正影响最终 hash 值。
  8. 如果 block_count 大于 1,函数会继续处理下一个 64 字节 block,重复“解析 block → 扩展 W 数组 → 64 轮压缩 → 更新 ctx 状态”的流程,直到所有 block 都处理完成。最后函数会检查 stack canary,如果栈没有被破坏,就恢复寄存器并返回。
  9. 所以整体来说,这个函数就是 SHA256 的核心压缩循环:它把输入数据按 64 字节 block 处理,生成消息扩展数组 **W[0]~W[63]**,执行 64 轮 SHA256 标准压缩运算,并把结果累加回 **ctx** 的 8 个 hash 状态值中。

>-----------------------------------------------------------------------------<
[19:47:18 561]x16=RW@0x120490d8[libkmsec.so]0x490d8, md5=1bdc833279a546e6181e482b93d805d7, hex=982f8a4291443771cffbc0b5a5dbb5e95bc25639f111f159a4823f92d55e1cab98aa07d8015b8312be853124c37d0c55745dbe72feb1de80a706dc9b74f19bc1c1699be48647beefc69dc10fcca10c246f2ce92daa84744adca9b05cda88f97652513e986dc631a8c82703b0c77f59bff30be0c64791a7d55163ca0667292914850ab72738211b2efc6d2c4d130d385354730a65bb0a6a762ec9c281852c7292a1e8bfa24b661aa8708b4bc2a3516cc719e892d1240699d685350ef470a06a1016c1a419086c371e4c774827b5bcb034b30c1c394aaad84e4fca9c5bf36f2e68ee828f746f63a5781478c8840802c78cfaffbe90eb6c50a4f7a3f9bef27871c622ae28d7982f8a42cd65ef23914437712f3b4deccffbc0b5bcdb8981a5dbb5e938b548f35bc2563919d005b6f111f1599b4f19afa4823f9218816ddad55e1cab420203a398aa07d8be6f7045015b83128cb2e44ebe853124e2b4ffd5c37d0c556f897bf2745dbe72b196163bfeb1de803512c725a706dc9b942669cf74f19bc1d24af19ec1699be4e3254f388647beefb5d58c8bc69dc10f659cac77cca10c2475022b596f2ce92d83e4a66eaa84744ad4fb41bddca9b05cb5531183da88f976abdf66ee52513e981032b42d6dc631a83f21fb98c82703b0e40eefbec77f59bfc28fa83df30be0c625a70a934791a7d56f8203e05163ca06706e0e0a67292914fc2fd246850ab72726c9265c38211b2eed2ac45afc6d2c4ddfb3959d130d3853de63af8b54730a65a8b2773cbb0a6a76e6aeed472ec9c2813b358214852c72926403f14ca1e8bfa2013042bc4b661aa89197f8d0708b4bc230be5406a3516cc71852efd619e892d110a96555240699d62a20715785350ef4b8d1bb3270a06a10c8d0d2b816c1a41953ab4151086c371e99eb8edf4c774827a8489be1b5bcb034635ac9c5b30c1c39cb8a41e34aaad84e73e363774fca9c5ba3b8b2d6f36f2e68fcb2ef5dee828f74602f17436f63a57872abf0a11478c884ec39641a0802c78c281e6323faffbe90e9bd82deeb6c50a41579c6b2f7a3f9be2b5372e3f27871c6
size: 768
0000: 98 2F 8A 42 91 44 37 71 CF FB C0 B5 A5 DB B5 E9    ./.B.D7q........
0010: 5B C2 56 39 F1 11 F1 59 A4 82 3F 92 D5 5E 1C AB    [.V9...Y..?..^..
0020: 98 AA 07 D8 01 5B 83 12 BE 85 31 24 C3 7D 0C 55    .....[....1$.}.U
0030: 74 5D BE 72 FE B1 DE 80 A7 06 DC 9B 74 F1 9B C1    t].r........t...
0040: C1 69 9B E4 86 47 BE EF C6 9D C1 0F CC A1 0C 24    .i...G.........$
0050: 6F 2C E9 2D AA 84 74 4A DC A9 B0 5C DA 88 F9 76    o,.-..tJ...\...v
0060: 52 51 3E 98 6D C6 31 A8 C8 27 03 B0 C7 7F 59 BF    RQ>.m.1..'....Y.
0070: F3 0B E0 C6 47 91 A7 D5 51 63 CA 06 67 29 29 14    ....G...Qc..g)).
0080: 85 0A B7 27 38 21 1B 2E FC 6D 2C 4D 13 0D 38 53    ...'8!...m,M..8S
0090: 54 73 0A 65 BB 0A 6A 76 2E C9 C2 81 85 2C 72 92    Ts.e..jv.....,r.
00A0: A1 E8 BF A2 4B 66 1A A8 70 8B 4B C2 A3 51 6C C7    ....Kf..p.K..Ql.
00B0: 19 E8 92 D1 24 06 99 D6 85 35 0E F4 70 A0 6A 10    ....$....5..p.j.
00C0: 16 C1 A4 19 08 6C 37 1E 4C 77 48 27 B5 BC B0 34    .....l7.LwH'...4
00D0: B3 0C 1C 39 4A AA D8 4E 4F CA 9C 5B F3 6F 2E 68    ...9J..NO..[.o.h
00E0: EE 82 8F 74 6F 63 A5 78 14 78 C8 84 08 02 C7 8C    ...toc.x.x......
00F0: FA FF BE 90 EB 6C 50 A4 F7 A3 F9 BE F2 78 71 C6    .....lP......xq.
0100: 22 AE 28 D7 98 2F 8A 42 CD 65 EF 23 91 44 37 71    ".(../.B.e.#.D7q
0110: 2F 3B 4D EC CF FB C0 B5 BC DB 89 81 A5 DB B5 E9    /;M.............
0120: 38 B5 48 F3 5B C2 56 39 19 D0 05 B6 F1 11 F1 59    8.H.[.V9.......Y
0130: 9B 4F 19 AF A4 82 3F 92 18 81 6D DA D5 5E 1C AB    .O....?...m..^..
0140: 42 02 03 A3 98 AA 07 D8 BE 6F 70 45 01 5B 83 12    B........opE.[..
0150: 8C B2 E4 4E BE 85 31 24 E2 B4 FF D5 C3 7D 0C 55    ...N..1$.....}.U
0160: 6F 89 7B F2 74 5D BE 72 B1 96 16 3B FE B1 DE 80    o.{.t].r...;....
0170: 35 12 C7 25 A7 06 DC 9B 94 26 69 CF 74 F1 9B C1    5..%.....&i.t...
0180: D2 4A F1 9E C1 69 9B E4 E3 25 4F 38 86 47 BE EF    .J...i...%O8.G..
0190: B5 D5 8C 8B C6 9D C1 0F 65 9C AC 77 CC A1 0C 24    ........e..w...$
01A0: 75 02 2B 59 6F 2C E9 2D 83 E4 A6 6E AA 84 74 4A    u.+Yo,.-...n..tJ
01B0: D4 FB 41 BD DC A9 B0 5C B5 53 11 83 DA 88 F9 76    ..A....\.S.....v
01C0: AB DF 66 EE 52 51 3E 98 10 32 B4 2D 6D C6 31 A8    ..f.RQ>..2.-m.1.
01D0: 3F 21 FB 98 C8 27 03 B0 E4 0E EF BE C7 7F 59 BF    ?!...'........Y.
01E0: C2 8F A8 3D F3 0B E0 C6 25 A7 0A 93 47 91 A7 D5    ...=....%...G...
01F0: 6F 82 03 E0 51 63 CA 06 70 6E 0E 0A 67 29 29 14    o...Qc..pn..g)).
0200: FC 2F D2 46 85 0A B7 27 26 C9 26 5C 38 21 1B 2E    ./.F...'&.&\8!..
0210: ED 2A C4 5A FC 6D 2C 4D DF B3 95 9D 13 0D 38 53    .*.Z.m,M......8S
0220: DE 63 AF 8B 54 73 0A 65 A8 B2 77 3C BB 0A 6A 76    .c..Ts.e..w<..jv
0230: E6 AE ED 47 2E C9 C2 81 3B 35 82 14 85 2C 72 92    ...G....;5...,r.
0240: 64 03 F1 4C A1 E8 BF A2 01 30 42 BC 4B 66 1A A8    d..L.....0B.Kf..
0250: 91 97 F8 D0 70 8B 4B C2 30 BE 54 06 A3 51 6C C7    ....p.K.0.T..Ql.
0260: 18 52 EF D6 19 E8 92 D1 10 A9 65 55 24 06 99 D6    .R........eU$...
0270: 2A 20 71 57 85 35 0E F4 B8 D1 BB 32 70 A0 6A 10    * qW.5.....2p.j.
0280: C8 D0 D2 B8 16 C1 A4 19 53 AB 41 51 08 6C 37 1E    ........S.AQ.l7.
0290: 99 EB 8E DF 4C 77 48 27 A8 48 9B E1 B5 BC B0 34    ....LwH'.H.....4
02A0: 63 5A C9 C5 B3 0C 1C 39 CB 8A 41 E3 4A AA D8 4E    cZ.....9..A.J..N
02B0: 73 E3 63 77 4F CA 9C 5B A3 B8 B2 D6 F3 6F 2E 68    s.cwO..[.....o.h
02C0: FC B2 EF 5D EE 82 8F 74 60 2F 17 43 6F 63 A5 78    ...]...t`/.Coc.x
02D0: 72 AB F0 A1 14 78 C8 84 EC 39 64 1A 08 02 C7 8C    r....x...9d.....
02E0: 28 1E 63 23 FA FF BE 90 E9 BD 82 DE EB 6C 50 A4    (.c#.........lP.
02F0: 15 79 C6 B2 F7 A3 F9 BE 2B 53 72 E3 F2 78 71 C6    .y......+Sr..xq.
^-----------------------------------------------------------------------------^

W20=h[0],W22=h[1],W23=h[2],W7=h[3],W3=h[4],W21=h[5],W19=h[6],W17=h[7]

>>> x0=0xe4fff208(-452988408) x1=0xe4fff0e8 x2=0x13bdcc1a x3=0x510e527f x4=0xa4aa9768 x5=0x20 x6=0x0 x7=0xa54ff53a x8=0xe4fff708 x9=0x0 x10=0x0 x11=0xe4fff290 x12=0x1 x13=0xe4fff213 x14=0xe4fff008
>>> x15=0xe4fff02c x16=0x120490d8 x17=0x5be0cd19 x18=0x82f8eb9c x19=0x1f83d9ab x20=0x6a09e667 x21=0x9b05688c x22=0xbb67ae85 x23=0x3c6ef372 x24=0xe4fff228 x25=0x15ca7889 x26=0x12082000 x27=0x68267da0 x28=0xe4fff610 fp=0xe4fff140
>>> q0=0xa54ff53a3c6ef372bb67ae856a09e667(-1.5671249868344602E-22, -5.763023256906666E-129) q1=0x5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c(8.245486516244437E136, 8.245486516244437E136) q2=0x1d6e6b681f181d1a6a691f6e686f1d6a(3.9383539207889827E204, 6.448301089030442E-167) q3=0x2f676e616c2f6176616a4c3b676e6972(1.848616432144324E161, 2.470160748865832E-80) q4=0x42014f584f44627507154940475c5a7d(1.5370245966123691E-274, 9.293203944548075E9) q5=0x40100401401004014010040140100401(4.003911019303815, 4.003911019303815) q6=0x0(0.0) q7=0x1f100000000000001f1(2.456E-321, 2.456E-321) q8=0x0(0.0) q9=0x0(0.0) q10=0x0(0.0) q11=0x0(0.0) q12=0x0(0.0) q13=0x0(0.0) q14=0x0(0.0) q15=0x0(0.0)
>>> q16=0x51f100000000000041f1(8.3403E-320, 1.0364E-319) q17=0x0(0.0) q18=0x51f100000000000041f1(8.3403E-320, 1.0364E-319) q19=0x0(0.0) q20=0x0(0.0) q21=0x0(0.0) q22=0x0(0.0) q23=0x0(0.0) q24=0x0(0.0) q25=0x0(0.0) q26=0x0(0.0) q27=0x0(0.0) q28=0x0(0.0) q29=0x0(0.0) q30=0x0(0.0) q31=0x0(0.0)
LR=RX@0x120169d4[libkmsec.so]0x169d4
SP=0xe4ffefe0
PC=RX@0x1201670c[libkmsec.so]0x1670c
nzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
sha256_transf + 0xe8
    [libkmsec.so  0x16708] [f71f4129] 0x12016708: "ldp w23, w7, [sp, #8]"
=> *[libkmsec.so *0x1670c]*[f203152a]*0x1201670c:*"mov w18, w21"
    [libkmsec.so  0x16710] [156a66b8] 0x12016710: "ldr w21, [x16, x6]"
    [libkmsec.so  0x16714] [e503162a] 0x12016714: "mov w5, w22"
    [libkmsec.so  0x16718] [e203172a] 0x12016718: "mov w2, w23"
    [libkmsec.so  0x1671c] [e103032a] 0x1201671c: "mov w1, w3"
    [libkmsec.so  0x16720] [e403142a] 0x12016720: "mov w4, w20"
    [libkmsec.so  0x16724] [5600054a] 0x12016724: "eor w22, w2, w5"
    [libkmsec.so  0x16728] [23188113] 0x12016728: "ror w3, w1, #6"
    [libkmsec.so  0x1672c] [5700050a] 0x1201672c: "and w23, w2, w5"
    [libkmsec.so  0x16730] [d602040a] 0x12016730: "and w22, w22, w4"
    [libkmsec.so  0x16734] [d602174a] 0x12016734: "eor w22, w22, w23"
    [libkmsec.so  0x16738] [d76966b8] 0x12016738: "ldr w23, [x14, x6]"
    [libkmsec.so  0x1673c] [b502110b] 0x1201673c: "add w21, w21, w17"
    [libkmsec.so  0x16740] [f103132a] 0x12016740: "mov w17, w19"
    [libkmsec.so  0x16744] [632cc14a] 0x12016744: "eor w3, w3, w1, ror #11"
    [libkmsec.so  0x16748] [5402010a] 0x12016748: "and w20, w18, w1"
.text:0000000000016624 ; __unwind {
.text:0000000000016624                 SUB             SP, SP, #0x170
.text:0000000000016628                 STP             X28, X23, [SP,#0x160+var_30] ; 保存旧寄存器
.text:000000000001662C                 STP             X22, X21, [SP,#0x160+var_20] ; 保存旧寄存器
.text:0000000000016630                 STP             X20, X19, [SP,#0x160+var_10] ; 保存旧寄存器
.text:0000000000016634                 STP             X29, X30, [SP,#0x160+var_s0] ; 保存旧 FP 和 LR 返回地址到栈上
.text:0000000000016638                 ADD             X29, SP, #0x160 ; 设置新的帧指针 FP,指向当前栈帧
.text:000000000001663C                 MRS             X8, TPIDR_EL0 ; 获取当前线程的 TLS 基地址
.text:0000000000016640                 LDR             X9, [X8,#0x28] ; 从 TLS + 0x28 位置读取 stack canary
.text:0000000000016644                 CMP             W2, #1  ; 比较第3个参数 block_count 是否小于1
.text:0000000000016648                 STUR            X9, [X29,#var_38] ; 保存 stack canary 到栈上,供函数返回时检测
.text:000000000001664C                 B.LT            loc_167FC ; 如果 block_count < 1,直接跳到函数结尾
.text:0000000000016650                 ADRP            X16, #sha256_k_ptr@PAGE ; 获取 sha256_k_ptr 所在页基地址
.text:0000000000016654                 LDR             X16, [X16,#sha256_k_ptr@PAGEOFF] ; sha256_k ; 获取 sha256_k 表的绝对地址
.text:0000000000016658                 ADD             X14, SP, #0x160+var_138 ; X14 = SP+0x28,栈上 W[64] 数组起始地址
.text:000000000001665C                 MOV             W9, WZR ; W9 = 0,初始化循环偏移
.text:0000000000016660                 MOV             X10, XZR ; X10 = 0,初始化循环计数器/偏移
.text:0000000000016664                 ADD             X11, X0, #0x88 ; X11 = ctx + 0x88,指向 SHA256 状态 h[0] 的地址
.text:0000000000016668                 MOV             W12, W2 ; block_count,保存循环次数
.text:000000000001666C                 ADD             X13, X1, #3 ; 计算 data + 3 的地址
.text:0000000000016670                 ADD             X15, X14, #0x24 ; '$' ; X15 = &W[9],用于生成 W[16] 起的扩展 word
.text:0000000000016674
.text:0000000000016674 loc_16674                               ; CODE XREF: sha256_transf+1D4↓j
.text:0000000000016674                 MOV             X17, XZR ;  X17 = 0,初始化循环偏移
.text:0000000000016678                 ADD             X18, X13, W9,SXTW ; X18 = data + 3 + block偏移,用作当前循环起始地址
.text:000000000001667C
.text:000000000001667C loc_1667C                               ; CODE XREF: sha256_transf+70↓j
.text:000000000001667C                 ADD             X1, X18, X17 ; 当前循环消息字 W[i] 的地址
.text:0000000000016680                 LDUR            W1, [X1,#-3] ; 实际从 data+block_offset+word_offset 读取4字节
.text:0000000000016684                 REV             W1, W1  ; 反转4字节字节序,转换为 SHA256 所需大端序
.text:0000000000016688                 STR             W1, [X14,X17] ; 把转换成大端序的32位word写入W[i]
.text:000000000001668C                 ADD             X17, X17, #4 ; 偏移加4字节,指向下一个W元素
.text:0000000000016690                 CMP             X17, #0x40 ; '@' ; 判断当前block前16个word是否处理完
.text:0000000000016694                 B.NE            loc_1667C ; 如果还没处理满 64 字节,继续循环处理下一个 W[i]
.text:0000000000016698                 LDR             W18, [SP,#0x160+var_138] ; 读取 W[0] 的值到 W18
.text:000000000001669C                 MOV             X17, XZR ; X17 = 0,初始化循环偏移
.text:00000000000166A0
.text:00000000000166A0 loc_166A0                               ; CODE XREF: sha256_transf+C0↓j
.text:00000000000166A0                 ADD             X1, X15, X17 ;  X1 = &W[9 + 当前偏移],第一次为 &W[9]
.text:00000000000166A4                 LDR             W3, [X1] ; 读取 W[i-7],第一次为 W[9]
.text:00000000000166A8                 LDR             W2, [X1,#0x14] ; 读取 W[i-2],用于扩展公式
.text:00000000000166AC                 ADD             X17, X17, #4 ; W数组偏移加4字节,相当于扩展下标加1
.text:00000000000166B0                 CMP             X17, #0xC0 ; 判断是否已经生成完 W[16]~W[63]
.text:00000000000166B4                 ADD             W18, W18, W3 ; W[i-16] + W[i-7],扩展公式第一步
.text:00000000000166B8                 LDUR            W3, [X1,#-0x20] ; 读取 W[i-15],用于 σ0(W[i-15])
.text:00000000000166BC                 EXTR            W4, W2, W2, #0x13 ; W4 = ROR(W2,19),也就是 W[i-2] 循环右移19位
.text:00000000000166C0                 EOR             W4, W4, W2,LSR#10 ; W4 ^= W[i-2] >> 10,计算 σ1(W[i-2]) 的一部分
.text:00000000000166C4                 EOR             W2, W4, W2,ROR#17 ; W2 = ROR(W[i-2],19) ^ SHR(W[i-2],10) ^ ROTR(W[i-2],17);
.text:00000000000166C8                 ADD             W18, W18, W2 ; W18 = W[i-16] + W[i-7] + σ1(W[i-2])
.text:00000000000166CC                 EXTR            W2, W3, W3, #0x12 ; W2=ROR(W3,18),也就是W[i-15]循环右移18位
.text:00000000000166D0                 EOR             W2, W2, W3,LSR#3 ; W2 ^= W[-15] >> 3
.text:00000000000166D4                 EOR             W2, W2, W3,ROR#7 ; W2 ^= ROR(W3,7)
.text:00000000000166D8                 ADD             W18, W18, W2 ; W[i] = W[i-16] + σ0(W[i-15]) + W[i-7] + σ1(W[i-2]);
.text:00000000000166DC                 STR             W18, [X1,#0x1C] ; 把计算得到的 W[i] 写入数组,第一次写入 W[16]
.text:00000000000166E0                 MOV             W18, W3 ; W18 = W[i-15];
.text:00000000000166E4                 B.NE            loc_166A0 ; 如果还没生成完 W[16]~W[63],继续扩展下一个 W[i]
.text:00000000000166E8                 LDR             Q0, [X11,#0x10] ; 读取 ctx + 0x88 偏移的 16 字节到 Q0
.text:00000000000166EC                 MOV             X6, XZR ; X6=0
.text:00000000000166F0                 STR             Q0, [SP,#0x160+var_150] ; 把 ctx+0x88+0x10 的16字节写入栈上临时缓冲区
.text:00000000000166F4                 LDR             Q0, [X11] ; 从 ctx + 0x88 读取 16 字节到 Q0
.text:00000000000166F8                 LDP             W19, W17, [SP,#0x160+var_150+8] ; 从栈上读取 ctx->h[6], ctx->h[7] 到 W19/W17
.text:00000000000166FC                 LDP             W3, W21, [SP,#0x160+var_150] ; 从栈上读取 ctx->h[4] 和 ctx->h[5] 到 W3/W21
.text:0000000000016700                 STR             Q0, [SP,#0x160+var_160] ;  把 ctx->h[0]~h[3] 的16字节写到栈上 SP+0
.text:0000000000016704                 LDP             W20, W22, [SP,#0x160+var_160] ; 从栈上读取 h[0] 和 h[1] 到 W20/W22
.text:0000000000016708                 LDP             W23, W7, [SP,#0x160+var_160+8] ; 从栈上读取 h[2] 和 h[3] 到 W23/W7
.text:000000000001670C
.text:000000000001670C loc_1670C                               ; CODE XREF: sha256_transf+174↓j
.text:000000000001670C                 MOV             W18, W21 ; W18 = 当前 f,保存旧 f,后面更新工作变量时 new_g = old_f
.text:0000000000016710                 LDR             W21, [X16,X6] ; W21 = K[i],X6 是字节偏移,每轮加 4
.text:0000000000016714                 MOV             W5, W22 ; W5 = 当前 b,保存旧 b,后面 new_c = old_b
.text:0000000000016718                 MOV             W2, W23 ; W2 = 当前 c,保存旧 c,后面 new_d = old_c
.text:000000000001671C                 MOV             W1, W3  ;  W1 = 当前 e,保存旧 e,后面参与 Σ1(e)、Ch(e,f,g),并且 new_f = old_e
.text:0000000000016720                 MOV             W4, W20 ; W4 = 当前 a,保存旧 a,后面参与 Σ0(a)、Maj(a,b,c),并且 new_b = old_a
.text:0000000000016724                 EOR             W22, W2, W5 ; W22 = c ^ b,用于计算 Maj(a,b,c)
.text:0000000000016728                 EXTR            W3, W1, W1, #6 ; W3 = ROR(e,6),开始计算大 Sigma1(e)
.text:000000000001672C                 AND             W23, W2, W5 ; W23 = c & b,用于计算 Maj(a,b,c)
.text:0000000000016730                 AND             W22, W22, W4 ; W22 = (b ^ c) & a
.text:0000000000016734                 EOR             W22, W22, W23 ; W22 = Maj(a,b,c) = (a & (b ^ c)) ^ (b & c)
.text:0000000000016738                 LDR             W23, [X14,X6] ;  W23 = W[i],从消息扩展数组读取当前轮 word
.text:000000000001673C                 ADD             W21, W21, W17 ; W21 = K[i] + h
.text:0000000000016740                 MOV             W17, W19 ; W17 = 当前 g,完成工作变量移动里的 new_h = old_g
.text:0000000000016744                 EOR             W3, W3, W1,ROR#11 ; W3 = ROR(e,6) ^ ROR(e,11)
.text:0000000000016748                 AND             W20, W18, W1 ; W20 = f & e,用于计算 Ch(e,f,g)
.text:000000000001674C                 BIC             W19, W17, W1 ; W19 = g & ~e,因为 BIC(a,b)=a & ~b,用于计算 Ch(e,f,g)
.text:0000000000016750                 EOR             W3, W3, W1,ROR#25 ; W3 = Σ1(e) = ROR(e,6) ^ ROR(e,11) ^ ROR(e,25)
.text:0000000000016754                 EOR             W19, W19, W20 ; W19 = Ch(e,f,g) = (e & f) ^ (~e & g)
.text:0000000000016758                 EXTR            W20, W4, W4, #2 ; W20 = ROR(a,2),开始计算大 Sigma0(a)
.text:000000000001675C                 ADD             W3, W21, W3 ;  W3 = h + K[i] + Σ1(e)
.text:0000000000016760                 EOR             W20, W20, W4,ROR#13 ; W20 = ROR(a,2) ^ ROR(a,13)
.text:0000000000016764                 ADD             W3, W3, W19 ; W3 = h + K[i] + Σ1(e) + Ch(e,f,g)
.text:0000000000016768                 EOR             W20, W20, W4,ROR#22 ;  W20 = Σ0(a) = ROR(a,2) ^ ROR(a,13) ^ ROR(a,22)
.text:000000000001676C                 ADD             W19, W3, W23 ; W19 = T1 = h + Σ1(e) + Ch(e,f,g) + K[i] + W[i]
.text:0000000000016770                 ADD             X6, X6, #4 ; X6 += 4,移动到下一轮 K[i+1] 和 W[i+1] 的字节偏移
.text:0000000000016774                 ADD             W3, W7, W19 ; W3 = new_e = old_d + T1
.text:0000000000016778                 ADD             W7, W20, W19 ; W7 = T1 + Σ0(a),后面还要加 Maj 得到 new_a
.text:000000000001677C                 CMP             X6, #0x100 ; 判断是否完成 64 轮,0x100 字节 / 4 = 64
.text:0000000000016780                 ADD             W20, W7, W22 ; W20 = new_a = T1 + Σ0(a) + Maj(a,b,c)
.text:0000000000016784                 MOV             W7, W2  ;  W7 = new_d = old_c
.text:0000000000016788                 MOV             W23, W5 ; W23 = new_c = old_b
.text:000000000001678C                 MOV             W22, W4 ;  W22 = new_b = old_a
.text:0000000000016790                 MOV             W19, W18 ; W19 = new_g = old_f
.text:0000000000016794                 MOV             W21, W1 ;  W21 = new_f = old_e
.text:0000000000016798                 B.NE            loc_1670C ; 如果 64 轮还没完成,继续下一轮 SHA256 压缩循环
.text:000000000001679C                 STP             W18, W17, [SP,#0x160+var_150+8] ; 保存最终工作变量 g、h 到栈上;此时 W18 等价于 final_g,W17 是 final_h
.text:00000000000167A0                 STP             W3, W1, [SP,#0x160+var_150] ; 保存最终工作变量 e、f 到栈上;W3 是 final_e,W1 是 final_f
.text:00000000000167A4                 STP             W20, W4, [SP,#0x160+var_160] ; 保存最终工作变量 a、b 到栈上;W20 是 final_a,W4 是 final_b
.text:00000000000167A8                 STP             W5, W2, [SP,#0x160+var_160+8] ; 保存最终工作变量 c、d 到栈上;W5 是 final_c,W2 是 final_d
.text:00000000000167AC                 LDP             W6, W7, [X0,#0x88] ; 读取原始 ctx->h[0]、h[1]
.text:00000000000167B0                 ADD             X10, X10, #1 ; 已处理 block 数加 1
.text:00000000000167B4                 CMP             X10, X12 ; 判断已经处理的 block 数是否等于总 block_count
.text:00000000000167B8                 ADD             W9, W9, #0x40 ; '@' ; 当前 data block 偏移加 64 字节,准备处理下一个 block
.text:00000000000167BC                 ADD             W6, W6, W20 ;  ctx->h[0] = 原 h[0] + final_a
.text:00000000000167C0                 LDP             W19, W20, [X0,#0x90] ; 读取原始 ctx->h[2]、h[3]
.text:00000000000167C4                 ADD             W4, W7, W4 ; ctx->h[1] = 原 h[1] + final_b
.text:00000000000167C8                 STP             W6, W4, [X0,#0x88] ; 回写更新后的 h[0]、h[1]
.text:00000000000167CC                 ADD             W5, W19, W5 ; ctx->h[2] = 原 h[2] + final_c
.text:00000000000167D0                 LDP             W7, W19, [X0,#0x98] ; 读取原始 ctx->h[4]、h[5]
.text:00000000000167D4                 ADD             W2, W20, W2 ; ctx->h[3] = 原 h[3] + final_d
.text:00000000000167D8                 STP             W5, W2, [X0,#0x90] ; 回写更新后的 h[2]、h[3]
.text:00000000000167DC                 ADD             W3, W7, W3 ; ctx->h[4] = 原 h[4] + final_e
.text:00000000000167E0                 LDP             W20, W7, [X0,#0xA0] ; 读取原始 ctx->h[6]、h[7]
.text:00000000000167E4                 ADD             W1, W19, W1 ;  ctx->h[5] = 原 h[5] + final_f
.text:00000000000167E8                 STP             W3, W1, [X0,#0x98] ; 回写更新后的 h[4]、h[5]
.text:00000000000167EC                 ADD             W18, W20, W18 ;  ctx->h[6] = 原 h[6] + final_g
.text:00000000000167F0                 ADD             W17, W7, W17 ; ctx->h[7] = 原 h[7] + final_h
.text:00000000000167F4                 STP             W18, W17, [X0,#0xA0] ; 回写更新后的 h[6]、h[7]
.text:00000000000167F8                 B.NE            loc_16674 ; 如果还有 block 没处理完,跳回 loc_16674 继续处理下一个 64 字节 block
.text:00000000000167FC
.text:00000000000167FC loc_167FC                               ; CODE XREF: sha256_transf+28↑j
.text:00000000000167FC                 LDR             X8, [X8,#0x28] ; 重新从 TLS + 0x28 读取当前 stack canary
.text:0000000000016800                 LDUR            X9, [X29,#var_38] ; 从栈上取出函数入口时保存的 stack canary
.text:0000000000016804                 CMP             X8, X9  ;  比较当前 canary 和保存的 canary 是否一致
.text:0000000000016808                 B.NE            loc_16824 ;  如果 canary 不一致,说明栈可能被破坏,跳到 __stack_chk_fail
.text:000000000001680C                 LDP             X29, X30, [SP,#0x160+var_s0] ; 恢复旧帧指针 X29 和返回地址 X30
.text:0000000000016810                 LDP             X20, X19, [SP,#0x160+var_10] ; 恢复 X20、X19
.text:0000000000016814                 LDP             X22, X21, [SP,#0x160+var_20] ; 恢复 X22、X21
.text:0000000000016818                 LDP             X28, X23, [SP,#0x160+var_30] ; 恢复 X28、X23
.text:000000000001681C                 ADD             SP, SP, #0x170 ;  释放当前函数栈空间,恢复 SP
.text:0000000000016820                 RET                     ; 返回调用者
.text:0000000000016824 ; ---------------------------------------------------------------------------
.text:0000000000016824
.text:0000000000016824 loc_16824                               ; CODE XREF: sha256_transf+1E4↑j
.text:0000000000016824                 BL              .__stack_chk_fail ; 调用栈保护失败处理函数,通常表示栈溢出或栈破坏
.text:0000000000016824 ; } // starts at 16624
.text:0000000000016824 ; End of function sha256_transf

三.总结

总体来说,这个样本难度不算高,非常适合作为 Android 逆向入门练习。它的 native 层多次调用 Java 方法,对 unidbg 补环境有一定要求,同时核心加密逻辑本身并不复杂,主要实现的是标准的 HMAC-SHA256 算法。因此,分析这个样本既可以练习Java 层so 层之间的调用链追踪,也可以锻炼ARM64汇编指令、寄存器传参、内存读写、内存释放,以及 stack canary 栈保护机制等基础知识。整体来看,这是一个非常适合用来熟悉 JNI 调用unidbg 环境补全native 加密算法分析的练习样本。

免费评分

参与人数 2吾爱币 +2 热心值 +1 收起 理由
greendays + 1 + 1 热心回复!
buluo533 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
buluo533 发表于 2026-7-9 15:07
kulouxiaohai 发表于 2026-7-9 11:38
这方面学习方法能介绍下吗?

吾爱的官方教程,https://www.52pojie.cn/home.php? ... w=me&from=space
沙发
kulouxiaohai 发表于 2026-7-9 11:38
3#
tianmoshen123 发表于 2026-7-9 11:50
4#
flsyjkx 发表于 2026-7-9 13:00
看起来有点复杂,后半部分没怎么看懂
6#
 楼主| jie1525050 发表于 2026-7-9 16:06 |楼主
tianmoshen123 发表于 2026-7-9 11:50
基础有点薄弱,用AI辅助更容易理解

我这个样本的hmac-sha256的讲解不太到位,后续我会新出一个对该样本加密的完整分析
7#
tylertiger 发表于 2026-7-9 16:29
能看被隐藏的成交价不
8#
cccyyyy 发表于 2026-7-9 16:31
学习了,思路非常好,谢谢分享
9#
Yifeiw0420 发表于 2026-7-9 16:35
学习学习了
10#
zhangSen 发表于 2026-7-9 16:47
太牛了,怎么学的这么牛
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-10 16:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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