资源
1. com.maihan.tredian 2021版 淘最热点
2. 该 app 没有加壳 ,也没混淆。
登录抓包
POST: https://api.taozuiredian.com/api/v1/auth/login/sms
POST /api/v1/auth/login/sms HTTP/1.1
Content-Type: application/json
Connection: close
Charset: UTF-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 14; Pixel 6 Build/AP1A.240505.004)
Host: api.taozuiredian.com
Accept-Encoding: gzip
Content-Length: 566{"sign":"3c0bc27ca0e9a647f32f5e9751d0db63e38849b5","nonce":"8bfisg1718611936552","tzrd":"BwzXzSGFyiPstMIVuzTZb7LzTZzbXRJOFzpbQiIaT7ujUDo3\/3Itq4wx7VQB94J9yQcrD22YICXHDicUiOY8ggIARFsAfdxkYDBJCJN5ScgdFKnF1+ISjECffNemekpceZEtoWiE8Dw8qF5DYd\/RAGF7iNzRF3WoESa4CR2\/JzHhlwW4d8a2HNEPaNGcdwvomjmkQRh17mnDNufFD3YbHeoTId4Gz0h+IzLUuHCLgQFWoUK\/FPYa7epLPvJ0fi5U1wrV+FU+avqDNzGQVyeewhofZU5c511E0ITgSI27IrqBdCwvtpyW29F8T5dsHhmTrkKyJqs43AS\/fAapl7jYuzLz1+P7PPNEATv5y8GVTQJb+xYVZSVeyNpXmpSgkNIiSQVcRG8xw\/tAAOh6LpuZrx4Xay6OlulssTeYvnaAR1k=","timestamp":"1718611936","app_ver":"100"}
HTTP/1.1 400 Bad Request
Server: nginx
Date: Mon, 17 Jun 2024 08:12:03 GMT
Content-Type: application/json
Content-Length: 167
Cache-Control: no-cache, private
Access-Control-Allow-Origin: *
Vary: Origin
Connection: close{"code":1,"error":{"code":1,"ex_code":0,"exid":"9a16e02c3c1769177721bc0ece924941","message":"\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e","custom_params":[]},"success":false}
需要一下三个参数逆向:
sign: 3c0bc27ca0e9a647f32f5e9751d0db63e38849b5
nonce: 8bfisg1718611936552
tzrd: BwzXzSGFyiPstMIVuzTZb7LzTZzbXRJOFzpbQiIaT7ujUDo3\/3Itq4wx7VQB94J9yQcrD22YICXHDicUiOY8ggIARFsAfdxkYDBJCJN5ScgdFKnF1+ISjECffNemekpceZEtoWiE8Dw8qF5DYd\/RAGF7iNzRF3WoESa4CR2\/JzHhlwW4d8a2HNEPaNGcdwvomjmkQRh17mnDNufFD3YbHeoTId4Gz0h+IzLUuHCLgQFWoUK\/FPYa7epLPvJ0fi5U1wrV+FU+avqDNzGQVyeewhofZU5c511E0ITgSI27IrqBdCwvtpyW29F8T5dsHhmTrkKyJqs43AS\/fAapl7jYuzLz1+P7PPNEATv5y8GVTQJb+xYVZSVeyNpXmpSgkNIiSQVcRG8xw\/tAAOh6LpuZrx4Xay6OlulssTeYvnaAR1k=
java层分析
1. 搜索 login/sms
2. 搜索sign, tzrd 字段
可以定位到在 MhRequestUtil.a中
tzrd: Base64.encodeToString(AesUtil.b(jSONObject.toString().getBytes(), a.getBytes(), b.getBytes()), 2);
我们在hook 下 encodeToString ()看是不是这里。 encodeToString里面是个AES ,
String a = "AES/CBC/PKCS5Padding"; 从代码中可以看出 是 aes 自吐算法
Java.perform(function(){var Base64 = Java.use('android.util.Base64');var String =Java.use('java.lang.String');// Base64.encodeToString(AesUtil.b(jSONObject.toString().getBytes(), a.getBytes(), b.getBytes()), 2);Base64.encodeToString.overload("[B","int").implementation =function(input,flag){console.log(' hook encodeToString.overloads("[B","int") ...' );console.log(getStackTrace());var data= this.encodeToString(input,flag);console.log('res data origin: ', data);console.log('res data string: ', String.$new(data));return data;}// public static byte[] b(byte[] bArr, byte[] bArr2, byte[] bArr3) throws Exception {var AesUtil = Java.use('com.maihan.tredian.util.AesUtil');AesUtil.b.implementation= function(src,key,key2){ // key =PeMBjWOVbrMgElXO 写死 , key2: VTToNCiifIJ9c2coconsole.log(getStackTrace());console.log('src: ',src)console.log('key: ',key)console.log('key2: ',key2)var data= this.b(src,key,key2);console.log('res data origin: ', data);console.log('res data string: ', String.$new(data));return data;}function getStackTrace(){return Java.use('android.util.Log').getStackTraceString(Java.use("java.lang.Throwable").$new());}
})//frida -UF -l hook_sign_java.js
打印出:
src string: {"imei2":"null","device_name":"google Pixel 6","code":"6465","imei1":"null","phone":"18051116656","device_udid":"47cba2e1a0c4aed30ddf6687e096ab84","device_id":"2fd841981ec9f6dbb162d6bf7f93de5e","channel":"official","system":"1","from":"app","mac":"02:00:00:00:00:00","os_ver_code":"34","android_id":"e3de5277cf5deadf"}key string: PeMBjWOVbrMgElXOkey2 string: VTToNCiifIJ9c2co
可以看出上面的 几个数据都是 定2量的。
tzrd= Base64(AES(src,key,key2))
sign: TreUtil.sign(a(map, false, false))
sign 是so里面: System.loadLibrary("tre");
public static native String sign(String str);
先hook jni 函数,是可以直接hoo的。
Java.perform(function(){// hook jni var TreUtil = Java.use('com.maihan.tredian.util.TreUtil');TreUtil.sign.implementation = function(args){log('hook TreUtil.sign...')log(getStackTrace());log(' TreUtil.sign str: ',args);var data= this.sign(args);log(' TreUtil.sign res data: ',data);return data;}
})
主动调用
function call_sign(){Java.perform(function(){var TreUtil = Java.use('com.maihan.tredian.util.TreUtil');var res =TreUtil.sign('app_ver=100&nonce=x78b381718634336095×tamp=1718634336&tzrd=BwzXzSGFyiPstMIVuzTZb7LzTZzbXRJOFzpbQiIaT7ujUDo3/3Itq4wx7VQB94J9sH6Br/+3ggTlKNhMMCwxI6jU4tNNGc3FdWH77BqPbj8Z3sr6GqVExUuEtetRWq25SWVEnXflbfjvJKBzOb2KAhNYCPx1NP700luPmXN8vRfplwvjHR5uPdw4Ek+X2hLDnq6nl7LM5OkLYEUxztD7ZaP9tWs+62Te35F/Gne+5Yv1XIn1MmoBpHKrgpprn6i/Bg+K29wqxYHPdj0B7bQKB+sUtQX6Jm1li8cKogRBfEZMQgLc1nbbqdr9yHvWdrfFGIGgVRRn6IZc0+UbUkVppXa0g0PRsbVZx+XREEB+LSBbWRYvF1HWmlMsutj4rxP/54/32j+zW9BAIkEN6Uh9T1IoJ/EI5xYF9gNmKdap1vQ=') //直接java方式主动调用 jni函数 也是可以的log(res)})
}
Hook 这个是可以拿到
var MhRequestUtil = Java.use('com.maihan.tredian.net.MhRequestUtil');// public static String a(Map<String, String> map, boolean z, boolean z2) {// map: {nonce=v08rer1718617204105, tzrd=BwzXzSGFyiPstMIVuzTZb7LzTZzbXRJOFzpbQiIaT7ujUDo3/3Itq4wx7VQB94J9sH6Br/+3ggTlKNhMMCwxI6jU4tNNGc3FdWH77BqPbj/vF2YZf1INWi7KKGyEfahgGdD3G3I4mHOAx4Z96YwwDCeT0XfVarRvgvO3PYkw7/Yv7twVlbauY+aeUgNVm7dYNWzXn/4iVvu7vPLKw/mFBc2AEK17L8398+/mGp0QM9itquHM/0Jxo1Vd9MOlIulQ6XT/1bX3GMaYDnV6y2uLvqPZsaG2tG2TJoPPUD3b+tLP7Qn8XKQFU0w9hu04Osbkyc/LXdEva5R3c+CQGyYUdBZYV6fBatWAK58X4e4VOxmYuUrVY5U+jg/KG6wYuFZ7JpPpmzfD0t0p4YA84rp5K6H29+2xQueuEFbID0/T0do=, timestamp=1718617204, app_ver=100}MhRequestUtil.a.overload('java.util.Map', 'boolean', 'boolean').implementation = function(m,z,z2){log(' hook MhRequestUtil.a...' );log(m,z,z2);var hm = Java.cast(m,Java.use('java.util.HashMap'));log(hm.toString());var data= this.a(m,z,z2);log("res data: "+data)return data;}
从lib中找出 libtre.so 是32位的, 用 IDA 32位打开。 在 导出表里面.
需要手动消息第一个参数 JNIEnv *, 第二个 jclass
nonce: 随机数
private static String a() {Random random = new Random();StringBuffer stringBuffer = new StringBuffer();for (int i = 0; i < 6; i++) {stringBuffer.append("abcdefghijklmnopqrstuvwxyz0123456789".charAt(random.nextInt(36)));}return stringBuffer.toString() + System.currentTimeMillis();}
so层分析
unidbg 调用so
JNIclass: com.maihan.tredian.util.TreUtil
public static native String sign(String str);
frida hook java JNI 函数, 看输入输出就行。
静态方法 参数1:string
返回值: string
代码:
新建包: com.maihan.tredian.util
新建类: TreUtil
package com.maihan.tredian.util;import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
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.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;import java.io.File;public class TreUtil {private final AndroidEmulator emulator;private final VM vm;private final Module module;private final DvmClass TreUtil;private final boolean logging;TreUtil(boolean logging) {this.logging = logging;emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.pocket.snh48").addBackendFactory(new Unicorn2Factory(true)).build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析vm = emulator.createDalvikVM(); // 创建Android虚拟机vm.setVerbose(logging); // 设置是否打印Jni调用细节DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/maihan/tredian/util/libtre.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数,一个 cpp 中可以有动态注册也可以静态注册module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块TreUtil = vm.resolveClass("com/maihan/tredian/util/TreUtil");//原先在 app的路径}private void destroy() {IOUtils.close(emulator);if (logging) {System.out.println("destroy");}}public static void main(String[] args) throws Exception {TreUtil test = new TreUtil(true);String res = test.sign("app_ver=100&nonce=x78b381718634336095×tamp=1718634336&tzrd=BwzXzSGFyiPstMIVuzTZb7LzTZzbXRJOFzpbQiIaT7ujUDo3/3Itq4wx7VQB94J9sH6Br/+3ggTlKNhMMCwxI6jU4tNNGc3FdWH77BqPbj8Z3sr6GqVExUuEtetRWq25SWVEnXflbfjvJKBzOb2KAhNYCPx1NP700luPmXN8vRfplwvjHR5uPdw4Ek+X2hLDnq6nl7LM5OkLYEUxztD7ZaP9tWs+62Te35F/Gne+5Yv1XIn1MmoBpHKrgpprn6i/Bg+K29wqxYHPdj0B7bQKB+sUtQX6Jm1li8cKogRBfEZMQgLc1nbbqdr9yHvWdrfFGIGgVRRn6IZc0+UbUkVppXa0g0PRsbVZx+XREEB+LSBbWRYvF1HWmlMsutj4rxP/54/32j+zW9BAIkEN6Uh9T1IoJ/EI5xYF9gNmKdap1vQ=");System.out.println(res);test.destroy();}private String sign(String src) {StringObject res = TreUtil.callStaticJniMethodObject(emulator, "sign(Ljava/lang/String;)Ljava/lang/String;", src); // 执行Jni方法return res.getValue();}}