1.对APP进行抓包
发现无法捕获正常数据包,应该是有证书绑定问题,有SSL 测试机有JustTrustme也不行 ,目前猜测是因为APP内部混淆了绑定证书代码
发现有梆梆壳,用脱壳机脱一下继续分析
我们对SSL的握手包调用进行hook跟踪,从而在代码中定位到它的位置
// TraceOkHttp.js
Java.perform(function () {
/* ---------- 0. 想 hook 的类 & 方法 ---------- */
var targets = [
{ cls: 'com.android.org.conscrypt.NativeSsl', mtd: 'doHandshake' },
{ cls: 'com.google.android.gms.org.conscrypt.NativeSsl',mtd: 'doHandshake' },
{ cls: 'org.conscrypt.NativeSsl', mtd: 'doHandshake' } // 一些厂商把包名改回纯 org.conscrypt.*
];
var Throwable = Java.use('java.lang.Throwable');
var Log = Java.use('android.util.Log');
/* ---------- 1. 通用挂钩函数:遍历所有 overload ---------- */
function hookAllOverloads(className, methodName) {
try {
var Cls = Java.use(className);
Cls[methodName].overloads.forEach(function (ov) {
console.log('[+] Hooking ' +
className + '.' + methodName +
'(' + ov.argumentTypes.map(function (t) { return t.className; }).join(', ') + ')');
ov.implementation = function () {
/* ----- 打印参数 ----- */
for (var i = 0; i < arguments.length; i++) {
console.log('arg[' + i + '] → ' + arguments[i]);
}
/* ----- 打印调用栈 ----- */
console.log('---- stack ----\n' +
Log.getStackTraceString(Throwable.$new()).replace(/(\sat\s)/g, '\n$1'));
return ov.apply(this, arguments);
};
});
} catch (e) {
// 类不存在(ROM/APP 未使用对应 Conscrypt 版本),静默跳过
}
}
/* ---------- 2. 批量挂钩 ---------- */
targets.forEach(function (t) { hookAllOverloads(t.cls, t.mtd); });
console.log('[*] SSL Handshake loaded');
});
//frida -U -f com.byd.aeri.caranywhere -l .\TraceOkHttp.js
得到:
[+] Hooking com.android.org.conscrypt.NativeSsl.doHandshake()
[+] Hooking com.android.org.conscrypt.NativeSsl.doHandshake(java.io.FileDescriptor, int)
[*] SSL Handshake loaded
arg[0] → java.io.FileDescriptor@ec3c697
arg[1] → 2500
arg[0] → java.io.FileDescriptor@12d606d
arg[1] → 2500
arg[0] → java.io.FileDescriptor@fa7c084
arg[1] → 2500
---- stack ----
java.lang.Throwable
at com.android.org.conscrypt.NativeSsl.doHandshake(Native Method)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:196)
at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:153)
at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:116)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:186)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30)
at com.bumptech.glide.load.a.i.a(HttpUrlFetcher.java:104)
at com.bumptech.glide.load.a.i.a(HttpUrlFetcher.java:59)
at com.bumptech.glide.load.b.q$a.a(MultiModelLoader.java:97)
at com.bumptech.glide.load.engine.w.a(SourceGenerator.java:62)
at com.bumptech.glide.load.engine.DecodeJob.k(DecodeJob.java:299)
at com.bumptech.glide.load.engine.DecodeJob.i(DecodeJob.java:266)
at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
at com.bumptech.glide.load.engine.b.a$a$1.run(GlideExecutor.java:446)
从函数okhttp3.internal.connection.RealConnection.connectTls 去定位ssl的绑定位置
直接祖传脚本一把梭了SSL
/**
* 通用 SSL Pinning 绕过脚本
* 1) 绕过各类 X509/TrustManager 校验
* 2) 绕过 HostnameVerifier / OkHostnameVerifier
* 3) 绕过 OkHttp3/4 的 CertificatePinner
* 4) 覆盖 Conscrypt / TrustManagerImpl / WebViewClient / HttpsURLConnection / TrustKit 等常见路径
*/
function hookAllOverloads(targetClass, methodName, cb) {
try {
const klass = Java.use(targetClass);
const overloads = klass[methodName].overloads;
overloads.forEach(function (ov) {
ov.implementation = function () {
try {
return cb.call(this, ov, arguments);
} catch (e) {
console.log(`[!] ${targetClass}.${methodName} bypass error: ${e}`);
// 如果出错时继续调用原方法
return ov.apply(this, arguments);
}
};
});
console.log(`[+] Hooked ${targetClass}.${methodName} (${overloads.length} overloads)`);
} catch (e) {
// console.log(`[i] ${targetClass}.${methodName} not found: ${e}`);
}
}
setImmediate(function() {
Java.perform(function() {
// ------------------------------------------------------------------------------------
// 1) 构造完全信任的 TrustManager,并把它塞进 SSLContext.init
// ------------------------------------------------------------------------------------
try {
const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
const SSLContext = Java.use('javax.net.ssl.SSLContext');
const TrustManager = Java.registerClass({
name: 'com.frida.FakeTrustManager',
implements: [X509TrustManager],
methods: {
getAcceptedIssuers: function () { return []; },
checkClientTrusted: function (chain, authType) {},
checkServerTrusted: function (chain, authType) {}
}
});
const TrustManagers = [TrustManager.$new()];
SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
).implementation = function (km, tm, sr) {
console.log('[+] SSLContext.init() => replacing TrustManagers');
return this.init(km, TrustManagers, sr);
};
console.log('[+] Hooked SSLContext.init');
} catch (e) {
console.log('[i] SSLContext hook failed: ' + e);
}
// ------------------------------------------------------------------------------------
// 2) Conscrypt / TrustManagerImpl / X509TrustManagerExtensions
// (Android 7+ 常见证书校验入口)
// ------------------------------------------------------------------------------------
const tmImpls = [
'com.android.org.conscrypt.TrustManagerImpl',
'android.net.http.X509TrustManagerExtensions',
];
tmImpls.forEach(function(name) {
try {
const TM = Java.use(name);
// 常见 3 参数签名
if (TM.checkServerTrusted) {
TM.checkServerTrusted.overloads.forEach(function (ov) {
ov.implementation = function () {
let host = '';
try {
// 不同 Overload 第三个参数可能是 host、SSLSession、Socket 等,安全打印
host = arguments.length >= 3 ? arguments[2] : '';
} catch (e) {}
console.log(`[+] ${name}.checkServerTrusted() bypassed for host: ${host}`);
// 尽量返回原来的证书链或空数组,类型要与原方法返回一致
try {
return ov.returnType.className === 'java.util.List'
? Java.use('java.util.ArrayList').$new()
: arguments[0];
} catch(e) {
return arguments[0];
}
};
});
console.log(`[+] Hooked ${name}.checkServerTrusted (${TM.checkServerTrusted.overloads.length} overloads)`);
}
} catch (e) {
// console.log(`[i] ${name} not found: ${e}`);
}
});
// ------------------------------------------------------------------------------------
// 3) 直接 Hook 常见 checkServerTrusted / checkClientTrusted(反射式兜底)
// ------------------------------------------------------------------------------------
[
'javax.net.ssl.X509TrustManager',
'javax.net.ssl.X509ExtendedTrustManager'
].forEach(function (klass) {
hookAllOverloads(klass, 'checkServerTrusted', function (ov, args) {
console.log(`[+] ${klass}.checkServerTrusted bypassed`);
// 返回值是 void,直接 return
return;
});
hookAllOverloads(klass, 'checkClientTrusted', function (ov, args) {
console.log(`[+] ${klass}.checkClientTrusted bypassed`);
return;
});
});
// ------------------------------------------------------------------------------------
// 4) OkHttp3 / OkHttp4 CertificatePinner.check
// ------------------------------------------------------------------------------------
[
'okhttp3.CertificatePinner', // OkHttp 3.x
'okhttp3.internal.tls.CertificatePinner' // 某些版本 / 变体(少见)
].forEach(function (klass) {
hookAllOverloads(klass, 'check', function (ov, args) {
let hostname = '';
try { hostname = args[0]; } catch (e) {}
console.log(`[+] ${klass}.check(${hostname}) bypassed`);
return; // 不抛异常视为通过
});
});
// ------------------------------------------------------------------------------------
// 5) HostnameVerifier / OkHostnameVerifier:主机名校验直接返回 true
// ------------------------------------------------------------------------------------
hookAllOverloads('javax.net.ssl.HostnameVerifier', 'verify', function (ov, args) {
let host = '';
try { host = args[0]; } catch (e) {}
console.log(`[+] HostnameVerifier.verify(${host}) => true`);
return true;
});
// okhttp3.internal.tls.OkHostnameVerifier (OkHttp3/4)
[
'okhttp3.internal.tls.OkHostnameVerifier', // OkHttp 3.x/4.x
'com.datatheorem.android.trustkit.pinning.OkHostnameVerifier' // TrustKit
].forEach(function (klass) {
hookAllOverloads(klass, 'verify', function (ov, args) {
let host = '';
try { host = args[0]; } catch (e) {}
console.log(`[+] ${klass}.verify(${host}) => true`);
return true;
});
});
// ------------------------------------------------------------------------------------
// 6) WebViewClient.onReceivedSslError:直接调用 handler.proceed()
// ------------------------------------------------------------------------------------
try {
const WebViewClient = Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
console.log('[+] WebViewClient.onReceivedSslError bypassed, proceed()');
handler.proceed();
};
console.log('[+] Hooked WebViewClient.onReceivedSslError');
} catch (e) {
// console.log('[i] WebViewClient not found: ' + e);
}
// ------------------------------------------------------------------------------------
// 7) HttpsURLConnection.setDefaultHostnameVerifier 拦截替换
// ------------------------------------------------------------------------------------
try {
const HttpsURLConnection = Java.use('javax.net.ssl.HttpsURLConnection');
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function (verifier) {
console.log('[+] HttpsURLConnection.setDefaultHostnameVerifier blocked');
// 不调用原方法,保持我们自己的 true verifier(上面已经全局 hook 了)
return;
};
console.log('[+] Hooked HttpsURLConnection.setDefaultHostnameVerifier');
} catch (e) {
// console.log('[i] HttpsURLConnection not found: ' + e);
}
console.log('[*] SSL pinning bypass script loaded.');
});
});
//frida -U -f com.byd.aeri.caranywhere -l .\SSLbypass.js
bypass之后就可以抓到数据包了
2.对关键函数进行定位
在jadx中搜索一下
function hook_dyn_dex() {
Java.perform(function () {
//hook 动态加载的dex
Java.enumerateClassLoaders({ //枚举classLoader
onMatch: function (loader) {
try {
if (loader.findClass("com.byd.aeri.caranywhere.MyApplication")) {
console.log(loader);
Java.classFactory.loader = loader; //切换classloader
}
} catch (error) {
}
}, onComplete: function () {
}
});
let CheckCodeUtil = Java.use("com.bangcle.comapiprotect.CheckCodeUtil");
CheckCodeUtil["checkcode"].overload('java.lang.String', 'int', 'java.lang.String').implementation = function (str, i, str2) {
console.log(`CheckCodeUtil.checkcode is called: str=${str}, i=${i}, str2=${str2}`);
let result = this["checkcode"](str, i, str2);
console.log(`CheckCodeUtil.checkcode result=${result}`);
return result;
};
});
}
setImmediate(function() {
setTimeout(hook_dyn_dex, 1000);
});
//frida -U -f com.byd.aeri.caranywhere -l hookcheck.js
直接尝试hook这个checkcode看传入传出的值是什么
在对应的so文件中的导出表中找到了相关静态注册的函数
但是目标函数无法被解析
3.dump so & fix
function dump_so(so_name) {
Java.perform(function () {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var dir = currentApplication.getApplicationContext().getFilesDir().getPath();
var libso = Process.getModuleByName(so_name);
console.log("[name]:", libso.name);
console.log("[base]:", libso.base);
console.log("[size]:", ptr(libso.size));
console.log("[path]:", libso.path);
var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'rwx');
var libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}
dump_so("libencrypt.so")
然后在使用Sofixer修复
4.Unidbg模拟执行
package com.byd;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.SystemService;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.linux.file.MapsFileIO;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import unicorn.Arm64Const;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class byd_own extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("get path:"+pathname);
return null;
}
byd_own(){
// 创建模拟器实例
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.byd.aeri.caranywhere").build();
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
emulator.getSyscallHandler().addIOResolver(this);
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/byd/byd.apk"));
// 设置JNI
vm.setJni(this);
// 打印日志
vm.setVerbose(true);
new AndroidModule(emulator,vm).register(memory);;
// 加载目标SO
// DalvikModule dm = vm.loadLibrary("encrypt", true);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/byd/libencrypt_fixed.so"), true);
//获取本SO模块的句柄,后续需要用它
module = dm.getModule();
// 调用JNI OnLoad
dm.callJNI_OnLoad(emulator);
};
public static void main(String[] args) {
byd_own demo = new byd_own();
}
}
搭一个框架
function hookcheckcode(){
Java.perform(function() {
var base_addr = Module.findBaseAddress("libencrypt.so");
var real_addr = base_addr.add(0x253AC);
console.log(real_addr)
var obj = Java.use("java.lang.Object");
var String_java = Java.use('java.lang.String');
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
Interceptor.attach(real_addr, {
onEnter: function (args) {
console.log("hook success!")
console.log("arg0 :" + args[0]) //JNIENV
console.log("arg1 :" + args[1] ) //jclazz
console.log("arg2:"+Java.cast(args[2],String_java));
console.log("arg3:"+args[3].toInt32());
console.log("arg4:"+Java.cast(args[4],String_java));
// regs
console.log(JSON.stringify(this.context))
}
});
})
}
setImmediate(function() {
//延迟1秒调用Hook方法
setTimeout(hookcheckcode, 5000);
});
hook so层的目标函数的入参
arg0 :0x79c7cfafc0
arg1 :0x793b3ff2f4
arg2:F{"encryData":"81DCED420565FC031EE422D8E515A6C9AFAE09A9201D71528D09D4FAC05FBF9A8B62BD8633A1DD06420D7DFA2FC549CBF57AEB7347C0386FB5B9A119ED47D96961E68C72D6562722164FD64329DDBF70","identifier":"club100","reqTimestamp":"1756364152227","imeiMD5":"CEA5D7C213293CA1052B0D9C01AE9E97","sign":"BBe0BEd52Fc7D4250b64D2b9D267Cc232391F79","serviceDir":"v2_Goods.getSystemConfigImage","serverFlag":"e_shop","identifierType":2,"appChannel":"1","objective":""}
arg3:1
arg4:1756364152229
注意根据报错补环境
package com.byd;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.AssetManager;
import com.github.unidbg.linux.android.dvm.api.SystemService;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.android.dvm.wrapper.DvmBoolean;
import com.github.unidbg.linux.android.dvm.wrapper.DvmLong;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.linux.file.MapsFileIO;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import unicorn.Arm64Const;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
public class byd extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("get path:"+pathname);
if ("/proc/self/maps".equals(pathname) || ("/proc/" + emulator.getPid() + "/maps").equals(pathname)) {
return FileResult.success(new SimpleFileIO(oflags,new File("unidbg-android/src/test/resources/byd/maps"),pathname));
}
return null;
}
byd(){
// 创建模拟器实例
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.byd.aeri.caranywhere").build();
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
emulator.getSyscallHandler().addIOResolver(this);
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/byd/byd.apk"));
// 设置JNI
vm.setJni(this);
// 打印日志
vm.setVerbose(true);
//这个是虚拟模块
new AndroidModule(emulator,vm).register(memory);
// 加载目标SO
// DalvikModule dm = vm.loadLibrary("encrypt", true);
DalvikModule dm = vm.loadLibrary(new File("D:\\unidbg\\unidbg-android\\src\\test\\resources\\byd\\libencrypt_fixed.so"), true);
//获取本SO模块的句柄,后续需要用它
module = dm.getModule();
// 调用JNI OnLoad
dm.callJNI_OnLoad(emulator);
};
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature){
case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":{
return dvmClass.newObject(null);
}
case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{
String arg0 = varArg.getObjectArg(0).getValue().toString();
String arg1 = varArg.getObjectArg(1).getValue().toString();
System.out.println(arg0+"===="+arg1);
if(arg0.equals("ro.serialno")){
return new StringObject(vm, "unknown");
}
return new StringObject(vm,"");
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature){
case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":{
return vm.resolveClass("android/app/ContextImpl").newObject(null);
}
case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;":{
DvmClass clazz = vm.resolveClass("android/content/pm/PackageManager");
return clazz.newObject(signature);
}
case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{
StringObject serviceName = varArg.getObjectArg(0);
assert serviceName != null;
System.out.println(serviceName.toString());
return new SystemService(vm, serviceName.getValue());
}
case "android/net/wifi/WifiManager->getConnectionInfo()Landroid/net/wifi/WifiInfo;":{
return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);
}
case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":{
return new StringObject(vm,"da:c4:13:ef:95:aa");
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature){
case "android/os/Build->MODEL:Ljava/lang/String;":
return new StringObject(vm,"Pixel 4 XL");
case "android/os/Build->MANUFACTURER:Ljava/lang/String;":
return new StringObject(vm,"Google");
case "android/os/Build$VERSION->SDK:Ljava/lang/String;":
return new StringObject(vm,"23");
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
public String checkcode(){
//arg list
ArrayList<Object> params = new ArrayList<>(10);
//jnienv
params.add(vm.getJNIEnv());
//jclazz
params.add(0);
//str1 参数1
StringObject str1 = new StringObject(vm, "F{\"encryData\":\"81DCED420565FC031EE422D8E515A6C9AFAE09A9201D71528D09D4FAC05FBF9A8B62BD8633A1DD06420D7DFA2FC549CBF57AEB7347C0386FB5B9A119ED47D96961E68C72D6562722164FD64329DDBF70\",\"identifier\":\"club100\",\"reqTimestamp\":\"1756364152227\",\"imeiMD5\":\"CEA5D7C213293CA1052B0D9C01AE9E97\",\"sign\":\"BBe0BEd52Fc7D4250b64D2b9D267Cc232391F79\",\"serviceDir\":\"v2_Goods.getSystemConfigImage\",\"serverFlag\":\"e_shop\",\"identifierType\":2,\"appChannel\":\"1\",\"objective\":\"\"}");
params.add(vm.addLocalObject(str1));
//int 参数2
params.add(0);
//str2 参数3
StringObject str2 = new StringObject(vm, "1756364152227");
params.add(vm.addLocalObject(str2));
Number number = module.callFunction(emulator, 0x253ac, params.toArray());
StringObject res = vm.getObject(number.intValue());
return res.toString();
}
public static void main(String[] args) {
byd demo = new byd();
String encryptdata = demo.checkcode();
System.out.println("encryptdata:" + encryptdata);
}
}
JNIEnv->NewStringUTF("FafrZocObav5T/8BMNRtxb1zihl625/ZO1gymFEkGfYKiyDgDQI8gTc9WhBZgaQQL2n8LdBKIF+3bDhIkMs57S5Pp4BRxYdXDI+shiMTha1fKKrUj6P+B1NVNNwzp99V2f87dDqut7b8yGF8wHJ9dfCwgTFdcs7hMEPtfIOXtG6BlR4zTSlUdxx6v6Lo7RUj708J75WeCmZYxJB/iaWSk0MW3AYn95OKr9UtCUrn0PD6Gf3+p/bkisQQmSTvMXVmI/7AlwuVXgREw5ReilhEutOyWd0D9suXz9BLX2L979xNWgbMZjfH7hqmOaflWKiUTGA6w/35QDs4j5hTpTBxFK0NZj2h5yXZT3a8DHeIlGH84IqpwjojL7LVgzdnE7dQIde1dZBJ+EDYhR8K7uLvcquKeHUWQldwMPTNxIxkilT0tnG2PPNcfOJ7RdZaSx/pdrGe3g/wRcemvOvYjEr7QPlcVAZddUxDM+AHqZQhXPrRPJfYqLGsyNjHtSe8nL+IENLx06Qn1LLL3PYZE3nyF2HxEm1eOriHcLXwLg6rMNwpQwKWxgyJ78eJayKbVaJK+wnQdPpjBeXnaj5xyzOxkPA==") was called from RX@0x12026c64[libencrypt.so]0x26c64
5.分析加解密数据
上方调用地址是0x26c64,在IDA里去看看
这个结果的地址 应该是ret value 我们hook上一个地址查看一下0x26C60
emulator.attach().addBreakPoint(module.base+0x26C60);
!]
可以看到我们的结果来源于x1=RW@0x12565280 直接traceWrite一下
去libc.so中看看这个地址进行了什么操作
发现是memcpy函数,继续hook这个函数找上方来源
emulator.attach().addBreakPoint(0x1233c294, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
int length = registerContext.getIntArg(2);
UnidbgPointer destStr = registerContext.getPointerArg(0);
UnidbgPointer srcStr = registerContext.getPointerArg(1);
Inspector.inspect(srcStr.getByteArray(0,length),"memcpy src==" + srcStr + " dest == " + destStr);
return true;
}
});
查看base解码后的数据
0000: 69 FA D9 A1 C3 9B 6A FE 53 FF C0 4C 35 1B 71 6F i.....j.S..L5.qo
0010: 5C E2 86 5E B6 E7 F6 4E D6 0C A6 14 49 06 7D 82 \..^...N....I.}.
0020: A2 C8 38 03 40 8F 20 4D CF 56 84 16 60 69 04 0B ..8.@. M.V..`i..
0030: DA 7F 0B 74 12 88 17 ED DB 0E 12 24 32 CE 7B 4B ...t.......$2.{K
0040: 93 E9 E0 14 71 61 D5 C3 23 EB 21 88 C4 E1 6B 57 ....qa..#.!...kW
0050: CA 2A B5 23 E8 FF 81 D4 D5 4D 37 0C E9 F7 D5 76 .*.#.....M7....v
0060: 7F CE DD 0E AB AD ED BF 32 18 5F 30 1C 9F 5D 7C ........2._0..]|
0070: 2C 20 4C 57 5C B3 B8 4C 10 FB 5F 20 E5 ED 1B A0 , LW\..L.._ ....
0080: 65 47 8C D3 4A 55 1D C7 1E AF E8 BA 3B 45 48 FB eG..JU......;EH.
0090: D3 C2 7B E5 67 82 99 96 31 24 1F E2 69 64 A4 D0 ..{.g...1$..id..
00A0: C5 B7 01 89 FD E4 E2 AB F5 4B 42 52 B9 F4 3C 3E .........KBR..<>
00B0: 86 7F 7F A9 FD B9 22 B1 04 26 49 3B CC 5D 59 88 ......"..&I;.]Y.
00C0: FF B0 25 C2 E5 57 81 11 30 E5 17 A2 96 11 2E B4 ..%..W..0.......
00D0: EC 96 77 40 FD B2 E5 F3 F4 12 D7 D8 BF 7B F7 13 ..w@.........{..
00E0: 56 81 B3 19 8D F1 FB 86 A9 8E 69 F9 56 2A 25 13 V.........i.V*%.
00F0: 18 0E B0 FF 7E 50 0E CE 23 E6 14 E9 4C 1C 45 2B ....~P..#...L.E+
0100: 43 59 8F 68 79 C9 76 53 DD AF 03 1D E2 25 18 7F CY.hy.vS.....%..
0110: 38 22 AA 70 8E 88 CB EC B5 60 CD D9 C4 ED D4 08 8".p.....`......
0120: 75 ED 5D 64 12 7E 10 36 21 47 C2 BB B8 BB DC AA u.]d.~.6!G......
0130: E2 9E 1D 45 90 95 DC 0C 3D 33 71 23 19 22 95 3D ...E....=3q#.".=
0140: 2D 9C 6D 8F 3C D7 1F 38 9E D1 75 96 92 C7 FA 5D -.m.<..8..u....]
0150: AC 67 B7 83 FC 11 71 E9 AF 3A F6 23 12 BE D0 3E .g....q..:.#...>
0160: 57 15 01 97 5D 53 10 CC F8 01 EA 65 08 57 3E B4 W...]S.....e.W>.
0170: 4F 25 F6 2A 2C 6B 32 36 31 ED 49 EF 27 2F E2 04 O%.*,k261.I.'/..
0180: 34 BC 74 E9 09 F5 2C B2 F7 3D 86 44 DE 7C 85 D8 4.t...,..=.D.|..
0190: 7C 44 9B 57 8E AE 21 DC 2D 7C 0B 83 AA CC 37 0A |D.W..!.-|....7.
01A0: 50 C0 A5 B1 83 22 7B F1 E2 5A C8 A6 D5 68 92 BE P...."{..Z...h..
01B0: C2 74 1D 3E 98 C1 79 79 DA 8F 9C 72 CC EC 64 3C .t.>..yy...r..d<
继续跟 RW@0x1256c030
emulator.traceWrite(0x1256c030,0x1256c035);
往上继续跟踪调用栈
往上发现相同入参有AES加密调用
跳转到对应地址查看
这段代码中 WBACRAES_EncryptOneBlock
函数通过函数指针调用一个加密函数,但实际的加密函数定义并不在这段代码中,而是通过指针间接调用的。要找到实际的加密函数,需要查看 CWAESCipher
类的定义或者在代码的其他地方查找函数指针的赋值操作
需要注意最后一个参数为int
搜索相关类,并且最后参数为int的
用unidbg hook其中的PrepareAESMatrix 打一下调用栈
最终可以找到真正调用的函数为0x7fb8 CWAESCipher_Auth::WBACRAES_EncryptOneBlock
再次hook这个函数中的PrepareAESMatrix
第10轮的时候
找到了可以注入的点即可开始DFA攻击
public void callDfa(){
Debugger debugger = emulator.attach();
debugger.addBreakPoint(module.base+0x7ffc,new BreakPointCallback() {
UnidbgPointer pointer;
int num = 1; //m0xe4ffeb10
@Override
public boolean onHit(Emulator<?> emulator, long address) {
pointer = UnidbgPointer.pointer(emulator, 0xe4ffeb10L);
System.out.println("===num "+num);
if(num%9==0){
Random random = new Random();
int randomLine = random.nextInt(4); // 生成 0 到 3 之间的随机数
switch (randomLine) {
case 0:
pointer.setByte(randint(0, 3), (byte) randint(0, 0xff));
break;
case 1:
pointer.setByte(randint(8, 11), (byte) randint(0, 0xff));
break;
case 2:
pointer.setByte(randint(16, 19), (byte) randint(0, 0xff));
break;
case 3:
pointer.setByte(randint(24, 27), (byte) randint(0, 0xff));
break;
}
}
num+=1;
return true;
}
});
}
public void HookByConsoleDebugger() {
Debugger debugger = emulator.attach();
debugger.addBreakPoint(0x4033c280, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
int length = registerContext.getIntArg(2);
UnidbgPointer destStr = registerContext.getPointerArg(0);
UnidbgPointer srcStr = registerContext.getPointerArg(1);
Inspector.inspect(srcStr.getByteArray(0,length),"memcpy src==" + srcStr + " dest == " + destStr);
return true;
}
});
debugger.addBreakPoint(module.base+0x98b4,new BreakPointCallback(){
int num = 0;
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
num+=1;
// debugger.addBreakPoint(module.base+0x7FFc,new BreakPointCallback(){
// int num2 = 0;
// @Override
// public boolean onHit(Emulator<?> emulator, long address) {
// RegisterContext context = emulator.getContext();
// if(num==1){
// num2+=1;
// System.out.println("num====="+num2); //
// return false;
// }
// return true;
// }
// });
long x1 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X1).longValue();
emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
if(num==1){
Backend backend = emulator.getBackend();
byte[] bytes = backend.mem_read(x1, 0x10);
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
hexString.append(String.format("%02X", b & 0xFF));
}
String filename = "unidbg-android/src/test/java/com/byd/dfaAES.txt"; // 文件名
System.out.println("hexstring==="+hexString);
try {
FileWriter writer = new FileWriter(filename,true);
writer.write(hexString+"\n"); // 写入字符串
writer.close();
} catch (IOException e) {
System.err.println("写入文件时出现错误:" + e.getMessage());
}
}
return true;
}
});
return true;
}
});
}
import phoenixAES
data = """69FAD9A1C39B6AFE53FFC04C351B716F
9AFAD9A1C39B6A7353FF144C355B716F
6923D9A1769B6AFE53FFC0DD351BDD6F
69FAD9DCC39BAFFE53D7C04C4C1B716F
69FAD94BC39B41FE5305C04CD71B716F
69E0D9A16E9B6AFE53FFC0EC351BF66F
69FA8BA1C3746AFEF1FFC04C351B71EB
AEFAD9A1C39B6A5D53FF3C4C3513716F
F1FAD9A1C39B6A2053FFF94C3593716F
69FAF9A1C3956AFE42FFC04C351B7127
69FAD6A1C37D6AFE84FFC04C351B7190
94FAD9A1C39B6AF553FFEE4C3587716F
98FAD9A1C39B6AC253FFCF4C355A716F
69FA06A1C3B46AFE11FFC04C351B7112
69FAD96FC39B3CFE53B0C04CDA1B716F
69FAD98BC39B78FE53EDC04C761B716F
69FA26A1C3526AFE38FFC04C351B711B
69E8D9A1569B6AFE53FFC069351B056F
69FA0CA1C3C66AFE29FFC04C351B71D6
69D2D9A1259B6AFE53FFC09C351B506F
65FAD9A1C39B6AF953FFFE4C35E2716F
69A6D9A1CA9B6AFE53FFC0A2351BF16F
69FAD97DC39BF3FE53FEC04C071B716F
81FAD9A1C39B6AA653FF6C4C35CB716F
69FA4CA1C3FD6AFE24FFC04C351B7122
69FAB4A1C34B6AFE66FFC04C351B71C8
690AD9A1459B6AFE53FFC080351B8A6F
71FAD9A1C39B6AF353FF824C3537716F
69FA51A1C3016AFEF5FFC04C351B712A
69FA80A1C3F36AFE71FFC04C351B7166
69FAD9A0C39B75FE53DAC04C251B716F
69FAD994C39B47FE5364C04C7E1B716F
69FAD91DC39BC2FE539BC04C601B716F
69FAD9D6C39B1CFE533EC04C381B716F
6956D9A1239B6AFE53FFC0C9351B6F6F
69FAD6A1C3C86AFE5BFFC04C351B7112
6919D9A1E49B6AFE53FFC0BE351B1D6F
FAFAD9A1C39B6AA853FF594C350B716F
69FA47A1C38F6AFECDFFC04C351B717D
69FAD90AC39B46FE53D9C04CE11B716F
690DD9A17A9B6AFE53FFC00A351BEA6F
69FA24A1C3B96AFECFFFC04C351B7123
69FA8FA1C3EE6AFE81FFC04C351B7163
6987D9A1AA9B6AFE53FFC0FC351B3D6F
69FACFA1C3916AFE69FFC04C351B71E0
C3FAD9A1C39B6AA353FF124C3554716F
69FA11A1C3CA6AFEEEFFC04C351B711A
69FAD99BC39BB9FE53FAC04CEA1B716F
69FA90A1C38B6AFEC2FFC04C351B71D5
69FAFBA1C3C86AFE75FFC04C351B7150
69FAD95CC39B35FE53F9C04C721B716F
69FAD9B1C39BA5FE538CC04C891B716F
69FAD9D8C39BE6FE5369C04C4D1B716F
"""
with open('crackfile', 'w') as fp:
fp.write(data)
phoenixAES.crack_file('crackfile', [], True, False, verbose=2)
.\aes_keyschedule.exe AA648D03D06D1AC2CA9DEB861AC1388D 10
K00: A87F1002B7DBC9FF882DC51F8A7DCFAD
K01: 56F5857CE12E4C836903899CE37E4631
K02: A7AF426D46810EEE2F828772CCFCC143
K03: 13D75826555656C87AD4D1BAB62810F9
K04: 2F1DC1687A4B97A0009F461AB6B756E3
K05: 96ACD026ECE74786EC78019C5ACF577F
K06: 3CF70298D010451E3C68448266A713FD
K07: 208A56ABF09A13B5CCF25737AA5544CA
K08: 5C912207AC0B31B260F96685CAAC224F
K09: D602A6737A0997C11AF0F144D05CD30B
K10: AA648D03D06D1AC2CA9DEB861AC1388D
key : A87F1002B7DBC9FF882DC51F8A7DCFAD
结合我们上面得到数据 decode by base64 进行AES解密
即可得到正确输出