unidbg 算法还原术 · 某民宿 app 篇 · 上卷
本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
本来打算一篇写完的,发现太长,加上我拖延症严重,等到写好不知猴年马月,遂拆成 3 篇;
第一篇模拟执行,第二篇签名加密算法还原,第三篇 body 加密算法还原;
以前的我:
现在的我:
样本:
aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzEyMzU5NjMvaGlzdG9yeV92MjQy
抓包信息:
需要搞定的是签名和 body 加密;
X-TJH:签名;
Kf64g3……:请求 body;
jadx 定位参数;
具体就不说了,很容易定位到在这里;
签名是 encrypt 函数生成的,body 是 bodyEncrypt 生成的;
模拟调用
先用 fida 主动调用一波(参数当然是 hook 这个函数得到的,但是被我截断了,方便后边算法分析);
没毛病我们再来用 unidbg;
frida-rpc encrypt:
function call_encrypt(){
Java.perform(function () {
var Gundam = Java.use("com.tujia.gundam.Gundam");
var StrCls = Java.use('java.lang.String');
var arg0_str ="";
var arg0 = StrCls.$new(arg0_str);
var arg1_str = "Mozilla/5.0 (Linux; Android 8.1.0; OPPO R11st Build/OPM1.171019.011; wv)"
var arg1 = StrCls.$new(arg1_str);
var arg2_str = "LON=null;LAT=null;CID=226809753;LAC=42272;"
var arg2 = StrCls.$new(arg2_str);
var arg3_str = "{\"code\":null,\"parameter\":{\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":true,\"v\":\"C\"}}},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"\"}"
var arg3 = StrCls.$new(arg3_str);
var arg4 = arg3.getBytes().length;
var arg5 = 1630845461;
var ret = Gundam.encrypt(arg0,arg1,arg2,arg3,arg4,arg5);
console.log("#encrypt ret:" + ret);
} );
}
**frida-rpc bodyEncrypt:**
function call_bodyEncrypt(){
Java.perform(function () {
var Gundam = Java.use("com.tujia.gundam.Gundam");
var StrCls = Java.use('java.lang.String');
var arg0 = '1';
var arg1 = 1630808396;
var arg2_str = "YM0A2TMAIEWA3xMAMM1xTjQEUYGD0wZAYAh32AdgQATDzAZNZA33WAEIZAzzhAMMNAzyTDAkZMzTizYOYN11TTgMRcDThyNMZO44GTIAVQGDi5OONN2wWGMMUVWj1iMNOYxxGmUU";
var arg2 = StrCls.$new(arg2_str)
var arg3 = arg2.length();
var arg4_str = "{\"code\":null,\"parameter\":{\"activityTask\":{},\"defa"//'{"code":null,"parameter":{"activityTask":{},"defaultKeyword":"","abTest":{"AppSearchHouseList":"B","listabtest8":"B"},"returnNavigations":true,"returnFilterConditions":true,"specialKeyType":0,"returnGeoConditions":true,"abTests":{"T_login_831":{"s":true,"v":"A"},"searchhuojiatujia":{"s":true,"v":"D"},"listfilter_227":{"s":true,"v":"D"},"T_renshu_292":{"s":true,"v":"D"},"Tlisttest_45664":{"s":true,"v":"C"},"Tlist_168":{"s":true,"v":"C"},"T_LIST27620":{"s":false,"v":"A"}},"pageSize":10,"excludeUnitIdSet":null,"historyConditions":[],"searchKeyword":"","url":"","isDirectSearch":false,"sceneCondition":null,"returnAllConditions":true,"searchId":null,"pageIndex":0,"onlyReturnTotalCount":false,"conditions":[{"type":2,"value":"2021-09-05"},{"type":3,"value":"2021-09-06"},{"label":"广州","type":1,"value":"45"}]},"client":{"abTest":{},"abTests":{},"adTest":{"m1":"Color OS V5.2.1","m2":"ade40419318f085ff21b4776f2eef21f","m3":"armeabi-v7a","m4":"armeabi","m5":"100","m6":"2","m7":"2"},"api_level":260,"appFP":"qA/Ch2zqjORBz90YV34sUZpcFXFV6vzhmAISdTjYAFeqMBTtMUukzQFXqkDokr+sMau0bWClwjtk36nbrVBWVtKF9gYuplycneojVw1pIsBYHtUa5n0jFF50c6v1MBvzWJZFk/SirrTIzpJd4mMJ/J4F23BfgLbda7zzJhI8bpXMmglTI9M0hz5X3MMi5uS+/dt6ErjrDv44vY4Y8/1r5dahiujqI90q8/BWuNDxVqp2vGN5XILei7rc/JicoO6S+upWlrY/oAqkMhdmAbh1Anzjd/AZ7Q4HQubQJhsQNSL/T86A6uW0oC+mJHwOLnP8HKN0q2Fu3rTcKZ+Prbs/dcBHaWJi1C1tHZFza2O+1gUQTgvg+Kq57BvE6IjEhveT","appId":"com.tujia.hotel","appVersion":"260_260","appVersionUpdate":"rtag-20210803-183436-zhengyuan","batteryStatus":"full","buildTag":"rtag-20210803-183436-zhengyuan","buildVersion":"8.38.0","ccid":"51742042410923060391","channelCode":"qq","crnVersion":"254","devModel":"OPPO R11st","devToken":"18071adc03d2b69cdfa","devType":2,"dtt":"","electricity":"100","flutterPkgId":"277","gps":null,"kaTest":{"k1":"2_1_2","k2":"sdm660","k3":"ubuntu-16","k4":"R11st_11_A.43_200402","k5":"OPPO/R11st/R11s:8.1.0/OPM1.171019.011/1577198226:user/release-keys","k6":"R11st","k7":"OPM1.171019.011"},"latitude":"23.105868","locale":"zh-CN","longitude":"113.470269","networkType":"1","osVersion":"8.1.0","platform":"1","salt":"YM0A2TMAIEWA3xMAMM1xTjQEUYGD0wZAYAh32AdgQATDzAZNZA33WAEIZAzzhAMMNAzyTDAkZMzTizYOYN11TTgMRcDThyNMZO44GTIAVQGDi5OONN2wWGMMUVWj1iMNOYxxGmUU","screenInfo":"","sessionId":"a3e57f1c-03a8-3bc4-8709-fb36e7698ae4_1630806934108","tId":"21090509553913313246","tbTest":{"j1":"11d890e2","j2":"R11s","j3":"OPPO R11st","j4":"OPPO","j5":"OPPO","j6":"unknown","j7":"qcom","j8":"2.1.0 (ART)"},"traceid":"1630808396402_1630808396176_1630807074415","uID":"a3e57f1c-03a8-3bc4-8709-fb36e7698ae4","version":"260","wifi":null,"wifimac":"i5ZQ9aI14FDr9VMJ/ECIg7KAOE+Xev1/CrFoa53WLbE="},"psid":"d5a4fc67-5e67-47af-81e2-bac409860287","type":null,"user":null,"usid":null}'
var arg4 = StrCls.$new(arg4_str)
var arg5 =arg4.getBytes().length;
var myRc = Gundam.bodyEncrypt(arg0,arg1,arg2,arg3,arg4,arg5);
console.log("#bodyEncrypt ret:" + ret);
} );
}
发现和 hook 的结果一致,那就来用 unidbg 模拟执行;
unidbg encrypt:
unidbg 补环境简单说一下(想要补的又快又好,还是得看龙哥得 csdn 和星球
csdn:https://blog.csdn.net/qq_38851536
星球:https://t.zsxq.com/2F2J6au
首先先运行 JNI_OnLoad (如下代码第 40 行)
package com.test;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.hook.hookzz.*;
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.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class Test extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private static final String APP_PACKAGE_NAME = "com.xxx.hotel";
Test() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(APP_PACKAGE_NAME).build();
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("xxx.apk")); // 创建Android虚拟机 报错用绝对路径
vm.setVerbose(true); // 设置是否打印Jni调用细节
vm.setJni(this);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\tujia\\libxxx_encrypt.so"), true);//报错用绝对路径
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) throws FileNotFoundException {
Test test = new Test();
}
}
运行发现报错;
为啥呢,因为 unidbg 没有实现这个类,我们得喂给它;
补充的代码逻辑可以写到 AbstractJni.java 类 367 行那里;
(截图别骂了后面有完整代码)
也可以在我们的 Test 类中补,因为 Test 类继承了 AbstractJNI(上面的 26 行);
这里为了方便演示,我都补到 Test 类中;
补完一个就运行一次,看报错来补;
补完这个 JNI_OnLoad 就能运行成功了;
看日志都知道它是在做签名校验咯;
最后附上 JNI_OnLoad 能成功运行的完整代码;
(友情提示:标红的类 ALT + 回车可以快速导入(不会有人不知道吧
package com.tujia;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.hook.hookzz.*;
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.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class Test extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private static final String APP_PACKAGE_NAME = "com.tujia.hotel";
Test() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(APP_PACKAGE_NAME).build();
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("tujia.apk")); // 创建Android虚拟机
vm.setVerbose(true); // 设置是否打印Jni调用细节
vm.setJni(this);
DalvikModule dm = vm.loadLibrary(new File("unidbg-master\\unidbg-android\\src\\test\\java\\com\\tujia\\libtujia_encrypt.so"), true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "com/tujia/hotel/TuJiaApplication->getInstance()Lcom/tujia/hotel/TuJiaApplication;": {
return vm.resolveClass("com/tujia/hotel/TuJiaApplication").newObject(signature);
}
case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;":{
StringObject type = varArg.getObjectArg(0);
System.out.println(type);
String name = "";
if ("\"SHA1\"".equals(type.toString())) {
name = "SHA1";
} else {
name = type.toString();
System.out.println("else name: " + name);
}
try {
return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature){
case "com/tujia/hotel/TuJiaApplication->getPackageName()Ljava/lang/String;":{
return new StringObject(vm,"com.tujia.hotel");
}
case "com/tujia/hotel/TuJiaApplication->getPackageManager()Landroid/content/pm/PackageManager;":{
return vm.resolveClass("android/content/pm/PackageManager").newObject(signature);
}
case "java/security/MessageDigest->digest([B)[B":{
MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
byte[] input = (byte[]) varArg.getObjectArg(0).getValue();
byte[] result = messageDigest.digest(input);
return new ByteArray(vm,result);
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
public static void main(String[] args) throws FileNotFoundException {
Test test = new Test();
}
}
然后我们来主动调用 encrypt 函数;
之前呢,我都是这样调用的:
public void call_encrypt() {
DvmClass Native=vm.resolveClass("com/tujia/gundam/Gundam");
String methodSign = "encrypt(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IJ)Ljava/lang/String;";
Object arg0 = vm.addLocalObject(new StringObject(vm, ""));
String arg1_str = "Mozilla/5.0 (Linux; Android 8.1.0; OPPO R11st Build/OPM1.171019.011; wv)";
Object arg1 = vm.addLocalObject(new StringObject(vm, arg1_str));
String arg2_str = "LON=null;LAT=null;CID=226809753;LAC=42272;";
Object arg2 = vm.addLocalObject(new StringObject(vm, arg2_str));
String arg3_str ="{\"code\":null,\"parameter\":{\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":true,\"v\":\"C\"}}},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"\"}";
Object arg3 = vm.addLocalObject(new StringObject(vm, arg3_str));
int arg4 = arg3_str.getBytes(StandardCharsets.UTF_8).length;
long arg5 = 1630845461L;
Object ret = Native.callStaticJniMethodObject(emulator, methodSign, arg0,arg1,arg2,arg3,arg4,arg5);
System.out.println("call_encrypt ret:"+((DvmObject) ret).getValue());
}
String methodSign = "encrypt(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IJ)Ljava/lang/String;";
这个是 smali 的写法 (先用 apktool 反编译 apk, 然后在 smail 文件夹里根据类名找)
现在呢,在龙哥的熏陶下,都是这样调用;
public void call_encrypt() {
List<Object> list = new ArrayList<>();
DvmClass Native=vm.resolveClass("com/tujia/gundam/Gundam");
DvmObject<?> jclass =Native.newObject(null);
Object arg0 = vm.addLocalObject(new StringObject(vm, ""));
String arg1_str = "Mozilla/5.0 (Linux; Android 8.1.0; OPPO R11st Build/OPM1.171019.011; wv)";
Object arg1 = vm.addLocalObject(new StringObject(vm, arg1_str));
String arg2_str = "LON=null;LAT=null;CID=226809753;LAC=42272;";
Object arg2 = vm.addLocalObject(new StringObject(vm, arg2_str));
String arg3_str ="{\"code\":null,\"parameter\":{\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":true,\"v\":\"C\"}}},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"\"}";
Object arg3 = vm.addLocalObject(new StringObject(vm, arg3_str));
int arg4 = arg3_str.getBytes(StandardCharsets.UTF_8).length;
long arg5 = 1630845461L;
list.add(vm.getJNIEnv()); //第一个参数都是JNIEnv
list.add(vm.addLocalObject(jclass));//第一个参数都是jclass
list.add(arg0);
list.add(arg1);
list.add(arg2);
list.add(arg3);
list.add(arg4);
list.add(arg5);
Number number = module.callFunction(emulator, 0x36a9, list.toArray())[0];
String result = vm.getObject(number.intValue()).getValue().toString();
System.out.println("result: " + result);
}
22 行 0x36a9 是啥?
是 encrypt 函数的在 so 文件中的偏移地址;
哪里得到的?
上面成功运行完 JNI_OnLoad,会显示动态注册的函数地址;
RegisterNative(com/tujia/gundam/Gundam, encrypt(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IJ)Ljava/lang/String;, RX@0x400036a9[libtujia_encrypt.so]0x36a9)
RegisterNative(com/tujia/gundam/Gundam, bodyEncrypt(Ljava/lang/String;JLjava/lang/String;ILjava/lang/String;I)Ljava/lang/String;, RX@0x4000380d[libtujia_encrypt.so]0x380d)
两种方法,结果都是一样的;任君选择;
ps:如果还有人用的是老版本的 unidbg(比如在写这篇以前的我);
结果可能是下图的结果,应该是老版本第 6 个参数长整型参数有 bug,导致第六个参数是 0;
解决办法:拥抱新版本
https://github.com/zhkl0228/unidbg
bodyEncrypt 同理不再细说;
完整代码:
package com.tujia;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.hook.hookzz.*;
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.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class Test extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private static final String APP_PACKAGE_NAME = "com.tujia.hotel";
Test() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(APP_PACKAGE_NAME).build();
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("D:\\qxp\\Frida_hook\\Encrypt\\tujia.apk")); // 创建Android虚拟机
vm.setVerbose(true); // 设置是否打印Jni调用细节
vm.setJni(this);
DalvikModule dm = vm.loadLibrary(new File("D:\\qxp\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\tujia\\libtujia_encrypt.so"), true);
module = dm.getModule();
Debugger debugger = emulator.attach();
// debugger.addBreakPoint(module.base + 0x415C );
dm.callJNI_OnLoad(emulator);
}
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "com/tujia/hotel/TuJiaApplication->getInstance()Lcom/tujia/hotel/TuJiaApplication;": {
return vm.resolveClass("com/tujia/hotel/TuJiaApplication").newObject(signature);
}
case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;":{
StringObject type = varArg.getObjectArg(0);
System.out.println(type);
String name = "";
if ("\"SHA1\"".equals(type.toString())) {
name = "SHA1";
} else {
name = type.toString();
System.out.println("else name: " + name);
}
try {
return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature){
case "com/tujia/hotel/TuJiaApplication->getPackageName()Ljava/lang/String;":{
return new StringObject(vm,"com.tujia.hotel");
}
case "com/tujia/hotel/TuJiaApplication->getPackageManager()Landroid/content/pm/PackageManager;":{
return vm.resolveClass("android/content/pm/PackageManager").newObject(signature);
}
case "java/security/MessageDigest->digest([B)[B":{
MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
byte[] input = (byte[]) varArg.getObjectArg(0).getValue();
byte[] result = messageDigest.digest(input);
return new ByteArray(vm,result);
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
public void call_encrypt() {
List<Object> list = new ArrayList<>();
DvmClass Native=vm.resolveClass("com/tujia/gundam/Gundam");
DvmObject<?> jclass =Native.newObject(null);
Object arg0 = vm.addLocalObject(new StringObject(vm, ""));
String arg1_str = "Mozilla/5.0 (Linux; Android 8.1.0; OPPO R11st Build/OPM1.171019.011; wv)";
Object arg1 = vm.addLocalObject(new StringObject(vm, arg1_str));
String arg2_str = "LON=null;LAT=null;CID=226809753;LAC=42272;";
Object arg2 = vm.addLocalObject(new StringObject(vm, arg2_str));
String arg3_str ="{\"code\":null,\"parameter\":{\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":true,\"v\":\"C\"}}},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"\"}";
Object arg3 = vm.addLocalObject(new StringObject(vm, arg3_str));
int arg4 = arg3_str.getBytes(StandardCharsets.UTF_8).length;
long arg5 = 1630845461L;
list.add(vm.getJNIEnv());
list.add(vm.addLocalObject(jclass));
list.add(arg0);
list.add(arg1);
list.add(arg2);
list.add(arg3);
list.add(arg4);
list.add(arg5);
Number number = module.callFunction(emulator, 0x36a9, list.toArray())[0];
String result = vm.getObject(number.intValue()).getValue().toString();
System.out.println("result: " + result);
// DvmClass Native=vm.resolveClass("com/tujia/gundam/Gundam");
// String methodSign = "encrypt(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IJ)Ljava/lang/String;";
// Object arg0 = vm.addLocalObject(new StringObject(vm, ""));
// String arg1_str = "Mozilla/5.0 (Linux; Android 8.1.0; OPPO R11st Build/OPM1.171019.011; wv)";
// Object arg1 = vm.addLocalObject(new StringObject(vm, arg1_str));
// String arg2_str = "LON=null;LAT=null;CID=226809753;LAC=42272;";
// Object arg2 = vm.addLocalObject(new StringObject(vm, arg2_str));
// String arg3_str ="{\"code\":null,\"parameter\":{\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":true,\"v\":\"C\"}}},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"\"}";
// Object arg3 = vm.addLocalObject(new StringObject(vm, arg3_str));
// int arg4 = arg3_str.getBytes(StandardCharsets.UTF_8).length;
// long arg5 = 1630845461L;
// Object ret = Native.callStaticJniMethodObject(emulator, methodSign, arg0,arg1,arg2,arg3,arg4,arg5);
// System.out.println("call_encrypt ret:"+((DvmObject) ret).getValue());
}
public void get_bodyencrypt() throws FileNotFoundException {
List<Object> list = new ArrayList<>();
DvmClass Native=vm.resolveClass("com/tujia/gundam/Gundam");
DvmObject<?> jclass =Native.newObject(null);
String arg0_str = "1";
Object arg0 = vm.addLocalObject(new StringObject(vm, arg0_str));
long arg1 = 1630808396L;
String arg2_str = "YM0A2TMAIEWA3xMAMM1xTjQEUYGD0wZAYAh32AdgQATDzAZNZA33WAEIZAzzhAMMNAzyTDAkZMzTizYOYN11TTgMRcDThyNMZO44GTIAVQGDi5OONN2wWGMMUVWj1iMNOYxxGmUU";
Object arg2 = vm.addLocalObject(new StringObject(vm, arg2_str));
int arg3 = arg2_str.getBytes(StandardCharsets.UTF_8).length;
String arg4_str = "{\"code\":null,\"parameter\":{\"activityTask\":{},\"defa";//"{\"code\":null,\"parameter\":{\"activityTask\":{},\"defaultKeyword\":\"\",\"abTest\":{\"AppSearchHouseList\":\"B\",\"listabtest8\":\"B\"},\"returnNavigations\":true,\"returnFilterConditions\":true,\"specialKeyType\":0,\"returnGeoConditions\":true,\"abTests\":{\"T_login_831\":{\"s\":true,\"v\":\"A\"},\"searchhuojiatujia\":{\"s\":true,\"v\":\"D\"},\"listfilter_227\":{\"s\":true,\"v\":\"D\"},\"T_renshu_292\":{\"s\":true,\"v\":\"D\"},\"Tlisttest_45664\":{\"s\":true,\"v\":\"C\"},\"Tlist_168\":{\"s\":true,\"v\":\"C\"},\"T_LIST27620\":{\"s\":false,\"v\":\"A\"}},\"pageSize\":10,\"excludeUnitIdSet\":null,\"historyConditions\":[],\"searchKeyword\":\"\",\"url\":\"\",\"isDirectSearch\":false,\"sceneCondition\":null,\"returnAllConditions\":true,\"searchId\":null,\"pageIndex\":0,\"onlyReturnTotalCount\":false,\"conditions\":[{\"type\":2,\"value\":\"2021-09-05\"},{\"type\":3,\"value\":\"2021-09-06\"},{\"label\":\"大理州\",\"type\":1,\"value\":\"36\"}]},\"client\":{\"abTest\":{},\"abTests\":{},\"adTest\":{\"m1\":\"Color OS V5.2.1\",\"m2\":\"ade40419318f085ff21b4776f2eef21f\",\"m3\":\"armeabi-v7a\",\"m4\":\"armeabi\",\"m5\":\"100\",\"m6\":\"2\",\"m7\":\"5\"},\"api_level\":260,\"appFP\":\"qA/Ch2zqjORBz90YV34sUZpcFXFV6vzhmAISdTjYAFeqMBTtMUukzQFXqkDokr+sMau0bWClwjtk36nbrVBWVrjmrPTCkXFIraNHdgRVW/QT6g4eLWuM3hhP8qsWgGnrErk2KA+GFxr/OBRMYfV4l0v+TYUDZ5k4bUCUawafdLY5b3aC02SuOrqjW3jjrXiB/dt6ErjrDv44vY4Y8/1r5Z6ut/2BmcErxM37MniKpW6EZc8F4CjJ9S1KRTtEPJ2Kkd2Sd8602jqdgtssJ6QKXyx2+qsKvybydVe+zSTXQGn/T86A6uW0oC+mJHwOLnP8HKN0q2Fu3rTcKZ+Prbs/dcBHaWJi1C1tHZFza2O+1gUQTgvg+Kq57BvE6IjEhveT\",\"appId\":\"com.tujia.hotel\",\"appVersion\":\"260_260\",\"appVersionUpdate\":\"rtag-20210803-183436-zhengyuan\",\"batteryStatus\":\"full\",\"buildTag\":\"rtag-20210803-183436-zhengyuan\",\"buildVersion\":\"8.38.0\",\"ccid\":\"51742042410923060391\",\"channelCode\":\"qq\",\"crnVersion\":\"254\",\"devModel\":\"OPPO R11st\",\"devToken\":\"\",\"devType\":2,\"dtt\":\"\",\"electricity\":\"100\",\"flutterPkgId\":\"277\",\"gps\":null,\"kaTest\":{\"k1\":\"2_1_2\",\"k2\":\"sdm660\",\"k3\":\"ubuntu-16\",\"k4\":\"R11st_11_A.43_200402\",\"k5\":\"OPPO/R11st/R11s:8.1.0/OPM1.171019.011/1577198226:user/release-keys\",\"k6\":\"R11st\",\"k7\":\"OPM1.171019.011\"},\"latitude\":\"23.105714\",\"locale\":\"zh-CN\",\"longitude\":\"113.470271\",\"networkType\":\"1\",\"osVersion\":\"8.1.0\",\"platform\":\"1\",\"salt\":\"ZMmADTBAMIzAzxMAYMkxWjVEFY2TkwNMZAh2WAdAFATzmAZMYA3wTAEckAzT2AMMMAz52DAIZMzThzYMMN1xWTgYMcDD0yNNMO41zTIQUQGDx5OOZN2wDGMMEVWjziMNZYxxDmUA\",\"screenInfo\":\"\",\"sessionId\":\"a3e57f1c-03a8-3bc4-8709-fb36e7698ae4_1630844995950\",\"tId\":\"21090509553913313246\",\"tbTest\":{\"j1\":\"11d890e2\",\"j2\":\"R11s\",\"j3\":\"OPPO R11st\",\"j4\":\"OPPO\",\"j5\":\"OPPO\",\"j6\":\"unknown\",\"j7\":\"qcom\",\"j8\":\"2.1.0 (ART)\"},\"traceid\":\"1630845462612_1630845462444_1630845358749\",\"uID\":\"a3e57f1c-03a8-3bc4-8709-fb36e7698ae4\",\"version\":\"260\",\"wifi\":null,\"wifimac\":\"i5ZQ9aI14FDr9VMJ/ECIg7KAOE+Xev1/CrFoa53WLbE=\"},\"psid\":\"76938405-a463-464a-9c49-752022daf516\",\"type\":null,\"user\":null,\"usid\":null}";
Object arg4 = vm.addLocalObject(new StringObject(vm, arg4_str));
int arg5 = arg4_str.getBytes(StandardCharsets.UTF_8).length;
list.add(vm.getJNIEnv());
list.add(vm.addLocalObject(jclass));
list.add(arg0);
list.add(arg1);
list.add(arg2);
list.add(arg3);
list.add(arg4);
list.add(arg5);
Number number = module.callFunction(emulator, 0x380c+1, list.toArray())[0];
String result = vm.getObject(number.intValue()).getValue().toString();
System.out.println("result: " + result);
}
public static void main(String[] args) throws FileNotFoundException {
Test test = new Test();
test.call_encrypt();
test.get_bodyencrypt();
}
}
算法还原
encrypt:
签名是 40 位长度的 16 进制字符串,很快想到是 sha1;让我们来 see see 是不是;
把 so 文件扔进 ida;按 g 跳到 0x36a9(为什么搜这个?划上去再看一遍);
静态分析一波;
EncodeHTTP 函数好长, 我们从下往上看;
欲知后事如何
请看下回分解