
样本地址: aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzMwNjY3Mw==
抓包分析
当抓到 login 这个包的时候,会发现在请求体中有一个 signature 的参数,今天我们的目标就是它

84c7dd50e8192db401cfbc4b2d6b0565f41fd2e0 这个格式的字符串,长度 40 位,感觉有很大可能是 sha1 算法,不知道魔改没
打开 jadx,搜索一下 signature 这个字段,但是发现搜索出来了很多关键词,那就拿其他相关的关键词进行搜索
fdsOtp 这个字段挺特殊的,直接在 jadx 中搜索,发现了 3 个地方
进去之后发现就找到了 signature 这个字段,它调用了 LoginRequest 这个类的 b 方法

跟进去,最后就直接跟到了是 WTWEctUfLf 这个方法返回的结果

frida,启动
function main() { Java.perform(function () { var LoginEncryptUtil = Java.use("com.ximalaya.ting.android.loginservice.LoginEncryptUtil"); LoginEncryptUtil["WTWEctUfLf"].implementation = function (context, z, str) { console.log(`LoginEncryptUtil.WTWEctUfLf is called: context=${context}, z=${z}, str=${str}`); let result = this["WTWEctUfLf"](context, z, str); console.log(`LoginEncryptUtil.WTWEctUfLf result=${result}`); return result; }; })}
setImmediate(main);通过 hook,得到入参是一下这些参数:
LoginEncryptUtil.WTWEctUfLf is called: context=com.ximalaya.ting.android.xmloader.XMApplication@975842c, z=false, str=account=aqsgOa6SYFGEVnn8XH6KkcTVjfGFaVjlYYUHRIuf2/lypnQQ4NrBStm9weVzMdg8yaYGgyX4dP/FdQ39TwXVXkE9ZnbeZCgbsQSPYycZ9tzBKs4e7+xw9yRmv7qZaQpPPK880mK408nf/wbLDvj6rb5FsN0rVP9cOj0pC5lhLgQ=&fdsOtp={"captcha_id":"3723312ce42a04b5c0b40e605a882037","lot_number":"fd6f39fe63c241c8b7a89e980dec5db6","pass_token":"da453db61a283377f65ccaa7ab1554c58b7090e564d03ad7b4d4fb6a25397401","gen_time":"1770081297","captcha_output":"2Z76G2CapbcJxb3z0NRv6NWndrlIytoIjX7gLdfkJsQrgtBAoCtFZbq7qWs7QDkcQd2bgFHjUC0fpWFUpuIwd_k_JWPqHe9TBEOkJ1jqLFicEpmmAmdU2MEBYwncCxHVi33DvIRKYrwyoEByBsAa1SZ5X7mzDpdEWSMD5CpT3e4WcDXV-vnaQm-p5EaL0prpMKLRAaAfULuMBJJfkl3i6OAmTRc4uvulOTHp9dyw7BwQydb-aPAdat8c1xd9PMd9LO_PeIAdjPJ5oaVZSC5yzEtwgUg7CfIu90p6YDIwkUiBLfVCZVGT3a-y2eS9dcyLlGkAw1A898P__HBQHc8A4-D4lY5hm680D2jZwVLqqWr3GCeKyzOcR1fejMMH-RZIL-ocLdVl60cmTSXbRqNC-xH3CmQbFIBidbSXYUDDxXRKo7BuHaukYsTTuFmKfOzSV6L2kymVNIdw58k6eTRMbfvuL6bcwYaWmZ4_Ss02ZNt4UJeMcoQt733FkNET4fRPiAEwqzNxtesz-GAQ6Uyta3F84KBaFfY3Ix6tJVQVbQQ="}&nonce=0-22881C771F2437b725b395e8aa833f7a42e858ebd51c82129ebbb05eb53be0&password=EzHwdxwNPpKmd0jSSJO79ZQZa/Fu1lYTMGB2QBuRFNWTdtaxv2pIMECdzIdQHmfQkbaSQWjuSqCmw1Q3ca6foQ6TpfucYKV5sQNZodaIjhF6EIpua/SffV7gWgO2g+mzBk5j3muCSKZCuEduWgtCOaqkjVZvN+1LgaZSXqHrVdE=&LoginEncryptUtil.WTWEctUfLf result=560f3bc0e1556d8399375ae3a831fb2daf771421补环境
先用 unidbg 搭个架子
package com.ximalaya;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.*;import com.github.unidbg.memory.Memory;
import java.io.File;
//必须继承AbstractJnipublic class EncryptUtil extends AbstractJni { public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用 public static Memory memory; public static VM vm; public static Module module; public final DvmClass LoginEncryptUtil; // 1 构造方法--》用来初始化 public EncryptUtil(){ // 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位 // 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的 // 这个名字可以随便写,一般写成app的包名 以后可能会动 emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.ximalaya.ting.android").build();
// 2.获取内存对象(可以操作内存) memory = emulator.getMemory();
//3.设置安卓sdk版本(只支持19、23) memory.setLibraryResolver(new AndroidResolver(23));
// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动 vm = emulator.createDalvikVM(new File("apks/ximalaya/xmly.apk")); vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni vm.setVerbose(true); //是否展示调用过程的细节
// 5.加载so文件 DalvikModule dm = vm.loadLibrary("login_encrypt", false); // 以后会动 dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册 LoginEncryptUtil = vm.resolveClass("com.ximalaya.ting.android.loginservice.LoginEncryptUtil"); // 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。 module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件 }
//2 sign 成员方法--》主要用来解密 public void WTWEctUfLf(){ DvmObject<?> context = vm.resolveClass("android/app/Application", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null); String param_str = "account=aqsgOa6SYFGEVnn8XH6KkcTVjfGFaVjlYYUHRIuf2/lypnQQ4NrBStm9weVzMdg8yaYGgyX4dP/F\n" + "dQ39TwXVXkE9ZnbeZCgbsQSPYycZ9tzBKs4e7+xw9yRmv7qZaQpPPK880mK408nf/wbLDvj6rb5F\n" + "sN0rVP9cOj0pC5lhLgQ=\n" + "&fdsOtp={\"captcha_id\":\"3723312ce42a04b5c0b40e605a882037\",\"lot_number\":\"fd6f39fe63c241c8b7a89e980dec5db6\",\"pass_token\":\"da453db61a283377f65ccaa7ab1554c58b7090e564d03ad7b4d4fb6a25397401\",\"gen_time\":\"1770081297\",\"captcha_output\":\"2Z76G2CapbcJxb3z0NRv6NWndrlIytoIjX7gLdfkJsQrgtBAoCtFZbq7qWs7QDkcQd2bgFHjUC0fpWFUpuIwd_k_JWPqHe9TBEOkJ1jqLFicEpmmAmdU2MEBYwncCxHVi33DvIRKYrwyoEByBsAa1SZ5X7mzDpdEWSMD5CpT3e4WcDXV-vnaQm-p5EaL0prpMKLRAaAfULuMBJJfkl3i6OAmTRc4uvulOTHp9dyw7BwQydb-aPAdat8c1xd9PMd9LO_PeIAdjPJ5oaVZSC5yzEtwgUg7CfIu90p6YDIwkUiBLfVCZVGT3a-y2eS9dcyLlGkAw1A898P__HBQHc8A4-D4lY5hm680D2jZwVLqqWr3GCeKyzOcR1fejMMH-RZIL-ocLdVl60cmTSXbRqNC-xH3CmQbFIBidbSXYUDDxXRKo7BuHaukYsTTuFmKfOzSV6L2kymVNIdw58k6eTRMbfvuL6bcwYaWmZ4_Ss02ZNt4UJeMcoQt733FkNET4fRPiAEwqzNxtesz-GAQ6Uyta3F84KBaFfY3Ix6tJVQVbQQ=\"}&nonce=0-22881C771F2437b725b395e8aa833f7a42e858ebd51c82129ebbb05eb53be0&password=EzHwdxwNPpKmd0jSSJO79ZQZa/Fu1lYTMGB2QBuRFNWTdtaxv2pIMECdzIdQHmfQkbaSQWjuSqCm\n" + "w1Q3ca6foQ6TpfucYKV5sQNZodaIjhF6EIpua/SffV7gWgO2g+mzBk5j3muCSKZCuEduWgtCOaqk\n" + "jVZvN+1LgaZSXqHrVdE=\n" + "&"; String ret = LoginEncryptUtil.newObject(null).callJniMethodObject(emulator,"WTWEctUfLf(Landroid/content/Context;ZLjava/lang/String;)Ljava/lang/String;",context,false,param_str).getValue().toString(); System.out.println(ret); }
// 3 main方法---》右键直接运行 public static void main(String[] args) { EncryptUtil loginEncryptUtil=new EncryptUtil(); loginEncryptUtil.WTWEctUfLf(); }}运行,报错
java.lang.UnsupportedOperationException: java/lang/String->toUpperCase()Ljava/lang/String; at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:933) at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:867) at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:69)补就是了
@Overridepublic DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { switch (signature){ case "java/lang/String->toUpperCase()Ljava/lang/String;":{ String str = (String) dvmObject.getValue(); System.out.println("输入参数==>"+str); return new StringObject(vm,str.toUpperCase()); } } return super.callObjectMethod(vm, dvmObject, signature, varArg);}接着,又报错,StringBuilder 没有初始化
java.lang.UnsupportedOperationException: java/lang/StringBuilder-><init>()V at com.github.unidbg.linux.android.dvm.AbstractJni.newObject(AbstractJni.java:753) at com.github.unidbg.linux.android.dvm.AbstractJni.newObject(AbstractJni.java:733) at com.github.unidbg.linux.android.dvm.DvmMethod.newObject(DvmMethod.java:224)继续补
@Overridepublic DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature){ case "java/lang/StringBuilder-><init>()V":{ return vm.resolveClass("java/lang/StringBuilder").newObject(new StringBuilder()); } } return super.newObject(vm, dvmClass, signature, varArg);}接着报错
java.lang.UnsupportedOperationException: java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest; at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:433) at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:422) at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethod(DvmMethod.java:54)接着补
@Overridepublic DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature){ case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;":{ String algorithm = varArg.getObjectArg(0).getValue().toString(); try { MessageDigest md = MessageDigest.getInstance(algorithm); return vm.resolveClass("java/security/MessageDigest").newObject(md); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);}接着就应该补 update 了
@Overridepublic void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { switch (signature){ case "java/security/MessageDigest->update([B)V":{ MessageDigest md = (MessageDigest)dvmObject.getValue(); byte[] data = (byte[]) varArg.getObjectArg(0).getValue(); md.update(data); return; } } super.callVoidMethod(vm, dvmObject, signature, varArg);}补 digest
case "java/security/MessageDigest->digest()[B":{ MessageDigest md = (MessageDigest)dvmObject.getValue(); return new ByteArray(vm,md.digest());}接着又报错
java.lang.UnsupportedOperationException: java/lang/Integer->toHexString(I)Ljava/lang/String; at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:433) at com.ximalaya.EncryptUtil.callStaticObjectMethod(EncryptUtil.java:100) at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:422)补上
case "java/lang/Integer->toHexString(I)Ljava/lang/String;":{ int value = varArg.getIntArg(0); String hex = Integer.toHexString(value); return new StringObject(vm,hex);}然后,又报错
java.lang.UnsupportedOperationException: java/lang/String->length()I at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethod(AbstractJni.java:965) at com.github.unidbg.linux.android.dvm.AbstractJni.callIntMethod(AbstractJni.java:938) at com.github.unidbg.linux.android.dvm.DvmMethod.callIntMethod(DvmMethod.java:129)补上
@Overridepublic int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { switch (signature){ case "java/lang/String->length()I":{ String str = (String) dvmObject.getValue(); return str.length(); } } return super.callIntMethod(vm, dvmObject, signature, varArg);}报错
java.lang.UnsupportedOperationException: java/lang/StringBuilder->append(Ljava/lang/String;)Ljava/lang/StringBuilder; at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:933) at com.ximalaya.EncryptUtil.callObjectMethod(EncryptUtil.java:74) at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:867)
java.lang.UnsupportedOperationException: java/lang/StringBuilder->append(I)Ljava/lang/StringBuilder; at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:933) at com.ximalaya.EncryptUtil.callObjectMethod(EncryptUtil.java:93) at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:867)补上
case "java/lang/StringBuilder->append(Ljava/lang/String;)Ljava/lang/StringBuilder;": { Object value = dvmObject.getValue(); if (value instanceof StringBuilder){ StringBuilder sb = (StringBuilder)value; sb.append(varArg.getObjectArg(0).getValue().toString()); return vm.resolveClass("java/lang/StringBuilder"); }}case "java/lang/StringBuilder->append(I)Ljava/lang/StringBuilder;":{ Object value = dvmObject.getValue(); if (value instanceof StringBuilder){ StringBuilder sb = (StringBuilder)value; sb.append(varArg.getIntArg(0)); return vm.resolveClass("java/lang/StringBuilder"); }}继续报错:
java.lang.UnsupportedOperationException: java/lang/StringBuilder->toString()Ljava/lang/String; at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:933) at com.ximalaya.EncryptUtil.callObjectMethod(EncryptUtil.java:93) at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:867)补上
case "java/lang/StringBuilder->toString()Ljava/lang/String;":{ StringBuilder sb = (StringBuilder)dvmObject.getValue(); return new StringObject(vm,sb.toString());}接着就出结果了

算法分析
根据前面的分析已经知道了算法是在 liblogin_encrypt.so 中,将这个 so 文件拖入到 ida 中进行分析
进入之后,找到 Java_com_ximalaya_ting_android_loginservice_LoginEncryptUtil_WTWEctUfLf 这个导出函数,发现有 ollvm

挺麻烦的,看着,直接用 unidbg 来 trace 一下这个代码
public void traceCode() { String traceFile = "unidbg-android/src/test/java/com/ximalaya/traceCode.log"; // 输出的路径 PrintStream traceStream = null; // 打印流 try { traceStream = new PrintStream(new FileOutputStream(traceFile), true); } catch (FileNotFoundException e) { throw new RuntimeException(e); } // traceCode 对代码进行监控 emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);}同时,hook 一下 memcpy,一般都会调用 libc.so 里面的这个工具函数
emulator.attach().addBreakPoint(module.findSymbolByName("memcpy").getAddress(), new BreakPointCallback() { @Override public boolean onHit(Emulator<?> emulator, long address) { RegisterContext registerContext = emulator.getContext(); UnidbgPointer src = registerContext.getPointerArg(1); int length = registerContext.getIntArg(2); Inspector.inspect(src.getByteArray(0, length), "memcpy:" + src.toString()); return true; }});然后得到了 trace 文件,首先来看一下密文是什么,从结果逆推 560f3bc0e1556d8399375ae3a831fb2daf771421,看着像 sha1 算法,单个字节开始搜索

发现都是从寄存器 r0 中进行取值,得到最终的结果,偏移为: 0x05b1b
到 ida 中跳转过去

hook 一下
emulator.attach().addBreakPoint(module.base+0x5B1A);发现 r0 就是结果值

接着 trace 一下
emulator.traceWrite(0x1220d018, 0x1220d018 +0x13);这儿有点奇怪的就是,没有看到哪里 write 之后,就已经得到了结果的值

然后我往上面看到之前补环境的日志

SHA1 ,他在请求参数后面拼接了一个固定的值: MOBILE-V1-PRODUCT-7D74899B338B4F348E2383970CC09991E8E8D8F2BC744EF0BEE94D76D718C089 然后 sha1 就没了

我了个这也太巧了吧,无所谓了,逆向就是这样,有些时候弄着弄着就出来了,有点怪
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 208 天
部分内容可能已过时
评论
还没有评论,来写第一条吧。