升本考试结束后,距离 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函数,因为p50和q50函数都是在这个类里面并且做的都是赋值返回的也都是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 初始化完成
这个初始化总体来说不难,主要就是
- 当前进程名 / 包名是否是 com.lianjia.beike
- SecManager.mContext 是否是正常 Application Context
- Application 类名是否是 com.homelink.android.MyApplication
- BuildConfig.DEBUG 是否为 false
- ApplicationInfo.flags 以及是否外部存储安装
- ActivityThread / LoadedApk 内部环境是否正常
- App 数据目录路径 /data/user/0/com.lianjia.beike
- APK 资源路径 base.apk
- 类 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,随后在 arg1、arg2、arg3 都存在的情况下进入签名计算流程,先将 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
- 这个函数本质是 HMAC-SHA256 的初始化函数(hmac_sha256_init),其主要作用是为后续的 HMAC 计算构建初始状态。函数首先接收
ctx、key 和 key_len 作为输入参数,并对 key 进行标准化处理:当 key 长度超过 64 字节时,会先对其进行 SHA256 压缩为 32 字节;否则直接使用原始 key。随后将 key 规范化为 64 字节的标准密钥块,以符合 HMAC 的 block size 要求。
- 在此基础上,函数在 ctx 内部分别构造两块 64 字节缓冲区,用于生成 inner pad(ipad)和 outer pad(opad)。其中在前
key_len 范围内,分别执行 key[i] ^ 0x36 生成 ipad,以及 key[i] ^ 0x5C 生成 opad;剩余未覆盖的部分分别用常量 0x36 和 0x5C 进行填充,从而保证两个 pad 都严格为 64 字节。
- 在实现层面,函数对 pad 构造过程进行了优化:当数据较大时使用 16 字节 NEON 向量指令进行并行处理以提升性能,同时在写入过程中还加入了内存重叠检测,避免 key 与目标缓冲区发生覆盖导致数据错误。
- 完成 pad 构造后,函数分别调用
sha256_init 和 sha256_update 对 inner pad 和 outer pad 初始化 SHA256 状态,得到 inner state 与 outer state,并将这两套状态保存到 ctx 内部作为备份,以便后续 HMAC 计算时可以直接复用该初始状态,无需重复构造 key 相关数据。
- 最后,函数还包含 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
- 这个函数本质是 SHA256 的 update 阶段实现,也就是
**sha256_update**。它的主要作用不是直接输出 hash,而是负责接收任意长度的输入数据,并把这些数据按照 SHA256 的规则整理成一个个 64 字节 block,然后交给 sha256_transf 去做真正的压缩计算。
- 函数开始时,会先从
ctx 中读取当前 buffer 里已经缓存了多少数据,也就是 buffer_len。因为 SHA256 每次只能处理 64 字节,所以如果上一次输入的数据没有凑满 64 字节,就会被暂存在 ctx->buffer 中。当前这次 update 进来后,函数会先计算这个 buffer 还剩多少空间可以填充。
- 接着,函数会先用
memcpy 把新输入数据的一部分复制到 ctx->buffer 后面,尝试把之前没满的 buffer 补齐。如果补完之后还是不足 64 字节,说明当前还不能触发 SHA256 压缩,于是函数只会更新 ctx 里的 buffer_len,然后直接返回,等待下一次 update 继续传入数据。
- 如果补完之后已经达到或超过 64 字节,函数就会先把当前
ctx->buffer 当作一个完整的 64 字节 block,调用 sha256_transf(ctx, buffer, 1) 执行一次压缩。这个时候,之前缓存的数据加上本次输入的数据,就真正参与了 SHA256 状态更新。
- 处理完这个补齐出来的 block 后,函数会继续看剩余的新输入数据里还有多少个完整的 64 字节 block。这里通过
len >> 6,也就是除以 64,计算可以直接处理多少个完整 block,然后继续调用 sha256_transf 对这些完整 block 进行压缩处理。
- 当所有完整的 64 字节 block 都处理完后,如果最后还剩下一些不足 64 字节的数据,函数不会丢掉它们,而是再次用
memcpy 把这些尾部数据保存回 ctx->buffer。这些数据会留到下一次 sha256_update,或者最后的 sha256_final 阶段继续处理。
- 最后,函数会更新
ctx 里的长度信息,包括已经处理过的总字节数,以及当前 buffer 中剩余的尾部数据长度。这样后续 update 或 final 才知道前面已经处理了多少数据、当前还缓存了多少数据。
- 所以整体来说,这个函数就是 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
- 这个函数是 SHA256 的 final 结束函数
**sha256_final**,它负责在所有数据通过 sha256_update 输入完成后,执行最后的 padding、最终压缩,并把 SHA256 的最终结果输出到 out 缓冲区中。
- 函数开始后,首先从
ctx 中读取两个关键值:一个是前面已经处理过的数据长度,另一个是当前 buffer 中还剩余、但尚未凑满 64 字节的数据长度。也就是说,sha256_update 已经把能处理的完整 64 字节 block 都处理掉了,而这个函数要处理的是最后剩下的尾部数据。
- 接下来函数会按照 SHA256 标准规则给最后的数据做 padding。它会先在当前 buffer 的末尾追加一个
0x80 字节,这个字节表示消息正文结束;然后在后面继续填充 0x00,直到剩余空间能够放下消息长度字段。如果当前 64 字节 block 剩余空间不够保存长度字段,函数就会额外再构造一个新的 64 字节 block,所以这里最终可能会处理 1 个 block,也可能会处理 2 个 block。
- 然后函数会计算消息总长度。注意这里保存的不是“字节长度”,而是“bit 长度”,所以代码里会把总字节数左移 3 位,也就是乘以 8。随后它会把这个 bit 长度按照大端格式写入 padding 区域的末尾。严格来说,SHA256 标准最后预留的是 8 字节长度字段;这段代码因为只处理 32 位长度,所以高 4 字节通过前面的
memset 保持为 0,低 4 字节写入真实的 bit 长度。
- padding 和长度字段都准备好后,函数会调用
sha256_transf(ctx, buffer, block_count),对最后补齐出来的 1 个或 2 个 64 字节 block 执行最终压缩。这个压缩完成后,SHA256 的 8 个内部状态值 H0 ~ H7 就已经变成最终 hash 状态。
- 最后,函数从
ctx + 0x88 开始依次读取这 8 个 32 位状态值,也就是 H0 ~ H7,然后把每个 32 位整数拆成 4 个字节,并按照大端字节序写入输出缓冲区 out。因为 SHA256 一共有 8 个状态值,每个状态值 4 字节,所以最终输出就是标准的 32 字节 SHA256 摘要。
- 所以整体来说,这个函数就是 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
- 这个函数是 SHA256 的核心压缩函数
**sha256_transf**,它是整个 SHA256 算法里真正执行哈希运算的地方。前面的 sha256_update 主要负责接收数据、缓存数据、把数据凑成 64 字节块;而这个函数负责把每一个 64 字节 block 送进 SHA256 的 64 轮压缩计算中,最终更新 ctx 里的 hash 状态。
- 函数的输入参数主要有三个:
X0 是 ctx,也就是 SHA256 上下文;X1 是待处理的数据地址;W2 是 block_count,表示这次要处理多少个 64 字节数据块。如果 block_count < 1,函数就不会进行压缩计算,直接跳到结尾返回。
- 进入正式处理后,函数会每次取出一个 64 字节 block。它先把这个 block 中的前 64 字节数据拆成 16 个 32 位整数,也就是
W[0] ~ W[15]。由于 SHA256 内部要求使用大端字节序,所以这里会通过 REV 指令把读取出来的 32 位数据进行字节序反转,然后保存到栈上的消息数组 W 中。
- 接着,函数会根据 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])。汇编里大量的 ROR、LSR、EOR、ADD,就是在计算这些小 sigma 函数和扩展结果。
- 消息扩展完成后,函数会从
ctx + 0x88 开始读取当前 SHA256 的 8 个状态值,也就是 h0 ~ h7,并把它们放入工作变量 a,b,c,d,e,f,g,h 中。后面的 64 轮压缩运算,就是围绕这 8 个工作变量进行不断更新。
- 在每一轮压缩中,函数都会读取一个常量
K[i] 和一个消息字 W[i],然后计算 SHA256 标准里的几个核心函数:Σ1(e)、Ch(e,f,g)、Σ0(a)、Maj(a,b,c),再组合出临时值 T1 和 T2。这些计算完成后,会更新 a,b,c,d,e,f,g,h 这 8 个工作变量,循环总共执行 64 轮。
- 当一个 block 的 64 轮计算结束后,函数不会直接丢弃这些工作变量,而是把最终的
a,b,c,d,e,f,g,h 分别加回原来的 ctx->h[0] ~ ctx->h[7]。这一步就是 SHA256 状态更新的关键:当前这个 64 字节 block 的内容,会通过这一步真正影响最终 hash 值。
- 如果
block_count 大于 1,函数会继续处理下一个 64 字节 block,重复“解析 block → 扩展 W 数组 → 64 轮压缩 → 更新 ctx 状态”的流程,直到所有 block 都处理完成。最后函数会检查 stack canary,如果栈没有被破坏,就恢复寄存器并返回。
- 所以整体来说,这个函数就是 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 加密算法分析的练习样本。