绕过更新 老版本5.7.8
1 2 3 4 frida-ps -Ua 查看包名com.aikucun.akapp objection -g com.aikucun.akapp explore -P ~/.objection/plugins android hooking list activities 查看所有Activity android intent launch_activity com.aikucun.akapp.activity.LoginActivity 跳到登录Activity不过被更新弹窗盖住
jadx反编译搜索"立即更新"
,直接hook掉show方法
1 2 3 4 5 6 7 8 9 10 function hook_sig(){ Java.perform(function(){ console.log("Entering hook") // 干掉弹窗 Java.use("com.aikucun.akapp.widget.dialog.ConfirmDialog").show.implementation = function(){ console.log("hook show ") } }) } setImmediate(hook_sig)
frida -U -f com.aikucun.akapp -l hook_sig.js –no-pause
signV1 抓包 https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch
appid 38741001 did 24c29bc14b5e3ddec1bf571c844a7e78 noncestr ae5a5c timestamp 1665846444 zuul 1 sig c5a0da1284344f608043722f45c85dfd934c0ce7
反编译搜索”sig”
上frida打印参数和返回值
1 2 3 4 5 6 7 8 9 10 11 12 var MXSecurity = Java.use('com.mengxiang.arch.security.MXSecurity') MXSecurity.signV1.implementation = function (a, b, c) { console.log('a: ', a); // url console.log('b: ', b); // noncestr console.log('c: ', c); // timestamp var res = this.signV1(a, b, c); // 加密返回 console.log('res: ', res); return res }
IDA打开libmx.so,搜索java发现signV1是静态注册函数
拼接了appid,noncestr,timestamp,secret,url等参数后调用digest函数进行加密,参数分别是JNIEnv,SHA1加密函数,加密内容的bytearray
如果需要还原算法,那么前提得拿到secret盐,可以通过jnitrace或者frida hook native都可以,或者直接使用unidbg黑盒调用。
Jnitrace 1 2 pip install jnitrace==3.0.8 jnitrace -l libmx.so -m spawn com.aikucun.akapp --ignore-vm > akc.log
Unidbg黑盒调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class AkuMx1 extends AbstractJni { // 初始化一些 apk 常量 private final AndroidEmulator emulator; private final VM vm; private final Module module; private final DvmClass mxSecurity; // APK 路径 public String apkPath = "C:\\Users\\Administrator\\Downloads\\爱库存5.7.8.apk"; // so 文件路径 public String soPath = "unidbg-android/src/test/resources/demo/akc/libmx.so"; // 加载指定版本的系统库 private static LibraryResolver createLibraryResolver() { return new AndroidResolver(23); } // 创建 android 模拟器,这里是 32 位的 private static AndroidEmulator createARMEmulator() { return AndroidEmulatorBuilder.for32Bit().build(); } AkuMx1() { emulator = createARMEmulator(); final Memory memory = emulator.getMemory(); // 设置 sdk版本 23 memory.setLibraryResolver(createLibraryResolver()); //创建DalvikVM,可以载入apk,也可以为null vm = emulator.createDalvikVM(new File(apkPath)); // 设置可以调用 jni 函数 vm.setJni(this); // 打印 jni 函数调用具体的 log vm.setVerbose(true); // 加载 so 文件 DalvikModule dm = vm.loadLibrary(new File(soPath), true); module = dm.getModule(); // 加载 java 加密函数所在 类 mxSecurity = vm.resolveClass("com/mengxiang/arch/security/MXSecurity"); } // 关闭模拟器 private void destroy() throws IOException { emulator.close(); System.out.println("destroy"); } public static void main(String[] args) throws IOException { AkuMx1 aku = new AkuMx1(); aku.run(); aku.destroy(); } public void run() { String a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch?appid=38741001&did=24c29bc14b5e3ddec1bf571c844a7e78&noncestr=af7aff×tamp=1665849838&zuul=1"; String b = "af7aff"; String c = "1665849838"; DvmObject<?> strRc = mxSecurity.callStaticJniMethodObject( emulator, "signV1(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", vm.addLocalObject(new StringObject(vm, a)), vm.addLocalObject(new StringObject(vm, b)), vm.addLocalObject(new StringObject(vm, c)) ); System.out.println("strRc: " + strRc.getValue()); } }
返回值确实空的,使用frida主动调用正常返回
1 2 3 4 5 6 7 8 9 function invoke_sig() { Java.perform(function () { var MXSecurity = Java.use('com.mengxiang.arch.security.MXSecurity'); var a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch?appid=38741001&did=24c29bc14b5e3ddec1bf571c844a7e78&noncestr=af7aff×tamp=1665849838&zuul=1"; var b = "af7aff"; var c = "1665849838"; console.log(MXSecurity.signV1(a, b, c)); }) }
通过jnitrace查看libmx.so的执行流可知需要先执行Java_com_mengxiang_arch_security_MXSecurity_init
,即public static final native int init(Context context, boolean z);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void run() { // 加载 context 上下文对象 DvmClass Context = vm.resolveClass("android/content/Context"); DvmObject<?> strRc1 = mxSecurity.callStaticJniMethodObject( emulator, "init(Landroid/content/Context;Z;)I;", // Context.newObject(null) 初始化对象,参数直接 null vm.addLocalObject(Context.newObject(null)), // frida hook打印入参为 false vm.addLocalObject(DvmBoolean.valueOf(vm,false)) ); System.out.println("strRc1: " + strRc1); String a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/search/product/image/switch?appid=38741001&did=24c29bc14b5e3ddec1bf571c844a7e78&noncestr=af7aff×tamp=1665849838&zuul=1"; String b = "af7aff"; String c = "1665849838"; DvmObject<?> strRc2 = mxSecurity.callStaticJniMethodObject( emulator, "signV1(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", vm.addLocalObject(new StringObject(vm, a)), vm.addLocalObject(new StringObject(vm, b)), vm.addLocalObject(new StringObject(vm, c)) ); System.out.println("strRc2: " + strRc2.getValue()); }
SHA256 是 android里的,java 里是 SHA-256,我们需要重写该函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;": StringObject type = vaList.getObjectArg(0); String name = ""; if ("\"SHA256\"".equals(type.toString())) { name = "SHA-256"; } else { name = type.toString(); System.out.println("else name: " + name); } try { return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name)); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } } return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList); }
1 2 3 4 5 6 7 8 if ("\"SHA256\"".equals(type.toString())) { name = "SHA-256"; } else if ("\"SHA1\"".equals(type.toString())) { name = "SHA-1"; } else { name = type.toString(); System.out.println("else name: " + name); }
signV3 分析 使用最新版本6.2.3,搜索"sign"
或者sign=
https://zuul.aikucun.com/api/gquery 查看业绩中心的数据
appid 38741001 did 0347d498ac2a5433d090a52dfe9315d1 noncestr 6ed25f subuserid 2aeb8f7cd6cb2aa69479c30366baf8f6 svs v3 timestamp 1666495412 token 831156285ba5413ba43ed83bfbdb38d0 userId 2aeb8f7cd6cb2aa69479c30366baf8f6 userid 2aeb8f7cd6cb2aa69479c30366baf8f6 sign e1c350a82dd7d8a22d21452ce4e47ab2201493eec0bc3ff3cb7174a4ef8727b1
尝试用objection hook一下MXSecurity的方法public static final native String signV3(@NotNull String str, @NotNull String str2, @NotNull String str3, @NotNull String str4);
1 2 3 4 5 6 com.aikucun.akapp on (Android: 10) [usb] # (agent) [374201] Called com.mengxiang.arch.utils.MD5Utils.b(java.lang.String) (agent) [374201] Arguments com.mengxiang.arch.utils.MD5Utils.b({"variables":{},"query":" query achieveCenterSalesBoard{\n saleBoardData:achieveCenterSalesBoard {\n orderCountShop\n saleShop\n averageTransactionValue\n customerCount\n }\n }\n ","operationName":"achieveCenterSalesBoard"}) (agent) [374201] Return Value: 1d1233cbfb6d5227fce0a483fd186ba6 (agent) [302898] Called com.mengxiang.arch.security.MXSecurity.signV3(java.lang.String, java.lang.String, java.lang.String, java.lang.String) (agent) [302898] Arguments com.mengxiang.arch.security.MXSecurity.signV3(https://zuul.aikucun.com/api/gquery?appid=38741001&did=0347d498ac2a5433d090a52dfe9315d1&noncestr=6ed25f&subuserid=2aeb8f7cd6cb2aa69479c30366baf8f6&svs=v3×tamp=1666495412&token=831156285ba5413ba43ed83bfbdb38d0&userId=2aeb8f7cd6cb2aa69479c30366baf8f6&userid=2aeb8f7cd6cb2aa69479c30366baf8f6, 6ed25f, 1666495412, 1d1233cbfb6d5227fce0a483fd186ba6) (agent) [302898] Return Value: e1c350a82dd7d8a22d21452ce4e47ab2201493eec0bc3ff3cb7174a4ef8727b1
str1是url,str2是noncestr,str3是timestamp,至于str4,可以看到有时候是null,有时候是32位字符串,通过两个请求的对比,发现GET请求时,str4为null,POST请求时,str4为32位字符串,Z1由Z1生成
String Z1 = Z1(request);
String sb2 = sb.toString();
Intrinsics.f(sb2, "builder.toString()");
String signV3 = MXSecurity.signV3(sb2, substring, valueOf, Z1);
即body的md5就是str4的值,body中的参数md5
IDA打开MXSecurity中引入的libmx.so找到Java_com_mengxiang_arch_security_MXSecurity_signV3
,最终返回的v35大概率由SHA256生成,传入digest函数的args[2]
v34的类型是jByteArray
digest上frida hook digest方法,MikManager中设置com.aikucun.akapp的启动方式是listen_wait,打开app,frida -U 爱库存 -l signv3.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function dump(name, addr, legnth) { console.log("======================== " + name + " ============"); console.log(hexdump(addr, { length: legnth || 32 })); } function hook_signV3() { // var bptr = Module.findBaseAddress("libmx.so"); var bptr = Module.findExportByName("libmx.so","_Z6digestP7_JNIEnvPKcP11_jbyteArray") console.log("==="+bptr) Interceptor.attach(bptr, { onEnter: function (args) { console.log("arg1:", args[1].readCString()); var sptr = Java.vm.tryGetEnv().getByteArrayElements(args[2]); console.log("arg2:", sptr.readCString()); }, onLeave: function (retval) { dump("sign", retval, 64); } }) } function main() { hook_signV3(); } setImmediate(main)
SHA256的输入,它是由appid
,svs
,noncestr
,timestamp
,secret(04fdc5e4d9c7420e896ee92b17c68e9f)
,url
构成,如果是POST请求,还会用"&"
与body的MD5拼接。
Python还原 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import hashlib import json from urllib.parse import urlencode def calc_signv3(url, params, body=None): data = [ ('appid', params['appid']), ('svs', params['svs']), ('noncestr', params['noncestr']), ('timestamp', params['timestamp']), ('secret', '04fdc5e4d9c7420e896ee92b17c68e9f'), ('url', url + '?' + urlencode(sorted(params.items()))), ] data = '&'.join(f'{k}={v}' for k, v in data) if body: if isinstance(body, (dict, list)): body = json.dumps(body, separators=(',', ':')) if isinstance(body, str): body = body.encode() s1 = hashlib.md5(body).hexdigest() data += '&' + s1 sign = hashlib.sha256(data.encode()).hexdigest() return sign
Unidbg黑盒调用
1 2 3 4 5 6 7 8 9 10 11 12 13 public void runSignV3() { List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); list.add(0); list.add(vm.addLocalObject(new StringObject(vm,"https://zuul.aikucun.com/akucun-member-aggregation/api/v2.0/user.do?action=phonelogin&appid=38741001&did=a93e299cbe12251f10f0356d06e6db1b&noncestr=e91f4f&svs=v3×tamp=1638341417"))); list.add(vm.addLocalObject(new StringObject(vm, "e91f4f"))); list.add(vm.addLocalObject(new StringObject(vm, "1638341417"))); list.add(vm.addLocalObject(new StringObject(vm, "5119e6c79149b40ea578fdab80489fbd"))); Number number = module.callFunction(emulator, 0x8254 + 1, list.toArray())[0]; String result = vm.getObject(number.intValue()).getValue().toString(); System.out.println("runSignV3:"+result); }
返回JNIEnv->NewStringUTF("") was called from unidbg@0xffff0000
,发现在MXSecurity 类中调用了init函数,可能用来配置一些变量
导出函数的地址是00007F14
1 2 3 4 5 6 7 8 9 10 11 public void call_init() { List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); list.add(0); DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null); list.add(vm.addLocalObject(context)); list.add(0); module.callFunction(emulator, 0x7F14 + 1, list.toArray()); } aku.call_init(); aku.runSignV3();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;": StringObject type = vaList.getObjectArg(0); String name = ""; if ("\"MD5\"".equals(type.toString())) { name = "MD5"; } else if ("\"SHA256\"".equals(type.toString())) { name = "SHA-256"; } else if ("\"SHA1\"".equals(type.toString())) { name = "SHA-1"; } else { name = type.toString(); System.out.println("else name: " + name); } try { return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name)); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } } return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList); }
接下来通过console debugger来分析输入,在类的构造函数中加入emulator.attach().addBreakPoint(module.base + 0x83FC);
,将v21处设置断点
重新运行代码
输入mr5打印寄存器数据
没有完整输出,看到r6
的值为0x24b
,它应该是数组的长度,输入mr5 0x24b
完整打印
c-跳过断点,n-下一步,d - Varibles 窗口 ,m - watch (mr0-mr7, mfp, mip, msp [size],m(address) [size])