某东算法分析

1.首先charles抓包发现每个请求Url后都接了一个sign的参数且每次都不一样。也没有其他的一些别的特别参数,那么关键问题就是分析sign参数的生成了

在这里插入图片描述

2.jadx反编译,寻找sign的生成的位置

直接搜索sign参数匹配的出来的结果太多了,一时间不好区分哪个是真的。于是使用getSign为前缀搜索

在这里插入图片描述
在这里插入图片描述

运气不错搜索到一个native方法 前缀也是getSign的方法名,打开这个类。找下引用位置进一步确认下是否为计算sign的类

在这里插入图片描述

最终在BaseApplication里面找到这个类引用,可以确定这个就是计算sign的类

3.开始分析sign方法的入参

frida hook这个方法得到结果如下:

function main(){
     Java.perform(function(){
       var BitmapkitUtils =  Java.use("com.jingdong.common.utils.BitmapkitUtils")
       BitmapkitUtils.getSignFromJni.overload('android.content.Context', 'java.lang.String', 'java.lang.String', 'java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function( context,  str,  str2,  str3,  str4,  str5){
             console.log("签名入参为: ","str         =>",str,"str2=>",str2,"str3=>",str3,"str4=>",str4,"str5=>",str5)
            var result = this.getSignFromJni(context,str,str2,str3,str4,str5)
             console.log('签名sign为 ',result)
            return result
      }
     })
 }

在这里插入图片描述

根据hook结果稍加分析大概知道各个参数代表的意义:

context:上下文对象

str:url路径

str2:请求body信息

str3:55a9c688729bb118 为KEY 16位长度,且每次请求都固定

str4:android 为平台

str5:版本号

查看源码大概得出str3 的为installationId生成规则是UUID 随机16位长度,在app第一次安装的时候创建保存在缓存中.
在这里插入图片描述

4.使用unidbg进行黑盒调用

1.第一步当然是补环境了
在这里插入图片描述

需要补充返回一个application对象,偷个懒想从AbstractJni 这里copy一份
在这里插入图片描述

重新运行一下又报错了,这个错是找不到对应的methodId、于是想着把Application换成Activity也可以,毕竟Activity也是可以获取Application对象的

@Override
     public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
         if ("com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;".equals(signature)) {
             //返回appliation
             return vm.resolveClass("android/app/Activity",
                    vm.resolveClass("android/content/ContextWrapper",
                             vm.resolveClass("android/content/Context"))).newObject(null);
        }
         return super.getStaticObjectField(vm, dvmClass, signature);
     }

重新运行就没问题了,接下来继续报错补环境,返回apk的路径。很简单

 @Override
 public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
     //sourceDir 代表当前apk目录
     if("android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;".equals(signature)){
         StringObject stringObject = new StringObject(vm, APK_PATH);
         return stringObject;
     }
     return super.getObjectField(vm, dvmObject, signature);
 }

在这里插入图片描述

图上这个返回刚开始不知道传具体什么参数,于是打算看看源码。结果没有反编译出来具体的函数实现。于是用frida hook了这个方法拿到图下的返回

第一个参数为:app安装目录 第二个参数为META-INF 目录 第三个是RSA
在这里插入图片描述

既然这样就直接返回RSA公钥了

@Override
     public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
         if ("com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B".equals(signature)) {
             byte[] unzip = vm.unzip("META-INF/xxxx.RSA");
             System.out.println("unzip " + new String(unzip));
             return new ByteArray(vm, unzip);
         }
         return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
     }

继续补充环境

@Override
 public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
     if ("sun/security/pkcs/PKCS7-><init>([B)V".equals(signature)) {
         DvmObject<?> objectArg = varArg.getObjectArg(0);
         try {
             PKCS7 pkcs7 = new PKCS7((byte[]) objectArg.getValue());
             return vm.resolveClass("sun/security/pkcs/PKCS7").newObject(pkcs7);
         } catch (ParsingException e) {
             e.printStackTrace();
         }
  
     }
     return super.newObject(vm, dvmClass, signature, varArg);
 }

继续补充环境,需补充如下
在这里插入图片描述

 @Override
     public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
         if ("sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;".equals(signature)) {
             PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();
             X509Certificate[] certificates = pkcs7.getCertificates();
             DvmObject<?> object = ProxyDvmObject.createObject(vm, certificates);
             return object;
  
  
         }
         return super.callObjectMethod(vm, dvmObject, signature, varArg);
     }

继续补充环境,从反编译的源码中copy 一份objectToBytes方法即可然后构造ByteArray返回即可

if("com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B".equals(signature)){
     DvmObject<?> objectArg = varArg.getObjectArg(0);
     byte[] bytes = objectToBytes(objectArg.getValue());
     return new ByteArray(vm,bytes);
 }

在这里插入图片描述

5.开始黑盒调用计算sign的方法

 List<Object> params = new ArrayList<>();
        params.add(dalvikVM.getJNIEnv());
        params.add(0);
        DvmClass context = dalvikVM.resolveClass("android/content/Context");
        params.add(dalvikVM.addLocalObject(context.newObject(null)));
        params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, "personinfoBusiness")));
        params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, "{\"callCJH\":\"1\",\"callNPS\":\"1\",\"closeJX\":\"0\",\"headTaskRefresh\":\"1\",\"locationArea\":\"0_0_0_0\",\"menuStaticSource\":\"0\",\"menuTimeStamp\":\"1631586010000\"}")));
        params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, INSTALL_ID)));
        params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, PLAT_FROM)));
        params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, VERSION)));
        Number numbers = moduleModule.callFunction(androidEmulator, 0x028B4+1, params.toArray())[0];

运行报错,还缺少环境

在这里插入图片描述

@Override
 public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
     if("java/lang/StringBuffer-><init>()V".equals(signature)){
         StringBuffer stringBuffer = new StringBuffer();
         return vm.resolveClass("java/lang/StringBuffer").newObject(stringBuffer);
     }
     return super.newObjectV(vm, dvmClass, signature, vaList);
 }

重新继续补充环境 这部分环境补充比较简单 直接上最后运行结果

在这里插入图片描述
黑盒调用没问题了,接下来开始分析sign具体是怎么生成的。全部代码如下:

package jd;
  
 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.linux.android.dvm.apk.Apk;
 import com.github.unidbg.linux.android.dvm.array.ArrayObject;
 import com.github.unidbg.linux.android.dvm.array.ByteArray;
 import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory;
 import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
 import com.github.unidbg.memory.Memory;
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import sun.security.pkcs.PKCS7;
 import sun.security.pkcs.ParsingException; 
 import java.io.*;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
  
 public class JD extends AbstractJni {
  
  
     private static final String SO_PATH = "";
     private static final String APK_PATH = "";
  
     private static final String INSTALL_ID = "55a9c688729bb118";
     private static final String PLAT_FROM = "android";
     private static final String VERSION = "10.4.6";
     private AndroidEmulator androidEmulator;
  
     public static void main(String[] args) {
         Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
         Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
         Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
         Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
         JD jd = new JD();
         jd.start();
     }
  
  
     public void start() {
         androidEmulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.jingdong.android")
                 .build();
          Memory androidEmulatorMemory = androidEmulator.getMemory();
          androidEmulatorMemory.setLibraryResolver(new AndroidResolver(23));
          VM dalvikVM = androidEmulator.createDalvikVM(new File(APK_PATH));
         DalvikModule module = dalvikVM.loadLibrary(new File(SO_PATH), false);
         dalvikVM.setJni(this);
         Module moduleModule = module.getModule();
         dalvikVM.callJNI_OnLoad(androidEmulator, moduleModule);
         List<Object> params = new ArrayList<>();
         params.add(dalvikVM.getJNIEnv());
         params.add(0);
         DvmClass context = dalvikVM.resolveClass("android/content/Context");
         params.add(dalvikVM.addLocalObject(context.newObject(null)));
         params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, "personinfoBusiness")));
         params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, "{\"callCJH\":\"1\",\"callNPS\":\"1\",\"closeJX\":\"0\",\"headTaskRefresh\":\"1\",\"locationArea\":\"0_0_0_0\",\"menuStaticSource\":\"0\",\"menuTimeStamp\":\"1631586010000\"}")));
         params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, INSTALL_ID)));
         params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, PLAT_FROM)));
         params.add(dalvikVM.addLocalObject(new StringObject(dalvikVM, VERSION)));
         Number numbers = moduleModule.callFunction(androidEmulator, 0x028B4 + 1, params.toArray())[0];
         DvmObject<?> object = dalvikVM.getObject(numbers.intValue());
         System.out.println("加密结果为:" + object.getValue());
     }
  
     @Override
     public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
         if ("com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;".equals(signature)) {
             //返回appliation
             return vm.resolveClass("android/app/Activity",
                     vm.resolveClass("android/content/ContextWrapper",
                             vm.resolveClass("android/content/Context"))).newObject(null);
         }
         return super.getStaticObjectField(vm, dvmClass, signature);
     }
  
     @Override
     public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
         if ("com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B".equals(signature)) {
             byte[] unzip = vm.unzip("META-INF/xx.RSA");
             System.out.println("unzip " + new String(unzip));
             return new ByteArray(vm, unzip);
         } else if ("com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B".equals(signature)) {
             DvmObject<?> objectArg = varArg.getObjectArg(0);
             byte[] bytes = objectToBytes(objectArg.getValue());
             return new ByteArray(vm, bytes);
         }
         return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
     }
  
     public static byte[] objectToBytes(Object obj) {
         try {
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
             objectOutputStream.writeObject(obj);
             objectOutputStream.flush();
             byte[] byteArray = byteArrayOutputStream.toByteArray();
             objectOutputStream.close();
             byteArrayOutputStream.close();
             return byteArray;
         } catch (IOException e) {
             return null;
         }
     }
  
     @Override
     public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
         if ("sun/security/pkcs/PKCS7-><init>([B)V".equals(signature)) {
             ByteArray byteArray = varArg.getObjectArg(0);
             try {
                 PKCS7 pkcs7 = new PKCS7(byteArray.getValue());
                 return vm.resolveClass("sun/security/pkcs/PKCS7").newObject(pkcs7);
             } catch (ParsingException e) {
                 e.printStackTrace();
             }
  
         }
         return super.newObject(vm, dvmClass, signature, varArg);
     }
  
     @Override
     public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
         if ("sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;".equals(signature)) {
             PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();
             X509Certificate[] certificates = pkcs7.getCertificates();
             DvmObject<?> object = ProxyDvmObject.createObject(vm, certificates);
             return object;
  
  
         }
         return super.callObjectMethod(vm, dvmObject, signature, varArg);
     }
  
     @Override
     public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
         //sourceDir 代表当前apk目录
         if ("android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;".equals(signature)) {
             StringObject stringObject = new StringObject(vm, APK_PATH);
             return stringObject;
         }
         return super.getObjectField(vm, dvmObject, signature);
     }
  
  
     @Override
     public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
         if ("java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;".equals(signature)) {
             StringBuffer stringBuffer = (StringBuffer) dvmObject.getValue();
             DvmObject<?> objectArg = vaList.getObjectArg(0);
             stringBuffer.append(objectArg.getValue().toString());
             return vm.resolveClass("java/lang/StringBuffer").newObject(stringBuffer);
         } else if ("java/lang/Integer->toString()Ljava/lang/String;".equals(signature)) {
             Integer integer = (Integer) dvmObject.getValue();
             return new StringObject(vm, integer.toString());
         }else if("java/lang/StringBuffer->toString()Ljava/lang/String;".equals(signature)){
             StringBuffer stringBuffer = (StringBuffer) dvmObject.getValue();
             return new StringObject(vm, stringBuffer.toString());
         }
         return super.callObjectMethodV(vm, dvmObject, signature, vaList);
     }
  
     @Override
     public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
         if ("java/lang/StringBuffer-><init>()V".equals(signature)) {
             StringBuffer stringBuffer = new StringBuffer();
             return vm.resolveClass("java/lang/StringBuffer").newObject(stringBuffer);
         } else if ("java/lang/Integer-><init>(I)V".equals(signature)) {
             int intArg = vaList.getIntArg(0);
             Integer integer = Integer.valueOf(intArg);
             return vm.resolveClass("java/lang/Integer").newObject(integer);
         }
         return super.newObjectV(vm, dvmClass, signature, vaList);
     }
 }

6.ida+unidbg分析sign的具体如何生成的

ida导入so 包 export导出中找到sign生成方法 截图如下:
在这里插入图片描述
F5生成伪代码
在这里插入图片描述
根据方法的具体逻辑修改了下方法名称 方便直观查看。初步观察是讲传入参数通过key=value 的方式拼接起来

通过unidbg的执行日志中也可以发现这点
在这里插入图片描述
接下来开始从函数返回的地方开始往上分析,单独调用执行返回如下:

st=1648374072802&sign=8102aefd41782d405174725cd433c5a0&sv=111

从上可知要分析的为st 、sign、sv这个三个值

1.st生成分析:生成一个时间戳 长度为13位的 用于计算sign

2.sign的生成:

分析伪代码找到sign的生成方法为sub_126AC,该方法上面的伪代码都是一些拼接字符串的操作,就是把入参拼接起来。
在这里插入图片描述
接下来开始unidbg hook该方法
在这里插入图片描述
然后开启无尽的S 单步执行往下走当程序走到switch判断时 发现bl指令跳转到sub_10de4函数 多次在这个位置debugger 到 发现 到 sv =111 走的是 case 2 还遇到过sv = 110 走的是case 1 、这部分我们只分析为2的情况也就是走sub_10DE4,至于为啥呢?就是因为这后面走的加密算法看起来简单些 像md5
在这里插入图片描述
查看下函数sub_10de4入参,hook该函数
打印mr1,为一串固定的字符串
在这里插入图片描述

mr2 为0x1 固定值

mr3 打印如下,为前面拼接的参数串 长度为0x106
在这里插入图片描述
接着分析sub_10de4 这个函数,点击进入sub_12ECC函数 ,这里为啥不分析sub_12FF0 呢 因为具体看了下没啥特别的。
在这里插入图片描述
hook sub_12ECC函数分析下入参
参数1:打印看看 ,看不太出来是什么 但是是64位 看后面的伪代码这个值主要是 跟 md5魔数有关系
在这里插入图片描述
在这里插入图片描述

参数2: 固定字符串 80306f4370b39fd5630ad0529f77adb6
在这里插入图片描述

参数3: 0x1

参数4: 为明文的参数拼接串

functionId=personinfoBusiness&body={“callCJH”:“1”,“callNPS”:“1”,“closeJX”:“0”,“headTaskRefresh”:“1”,“locationArea”:“0_0_0_0”,“menuStaticSource”:“0”,“menuTimeStamp”:“1631586010000”}&uuid=55a9c688729bb118&client=android&clientVersion=10.4.6&st=1648455172760&sv=111

在这里插入图片描述
参数5:为长度 0x106

开始分析代码

在这里插入图片描述

从上面分析 v27 应该就是 md5 魔数 数组咯 大致为

v27[1]= 0x68449237;

v27[2]= 0x7FCC3DA5;

v27[3] = 0x88D90FBB;

v27[4] = 0x5AE99AEE;

继续往下分析
在这里插入图片描述
这个区域是具体的实现的位置,flag 是之前传入的0x1 所以走的是if 判断 。这个do-while 大致是 从v27中取具体魔数 s 往下走 v18 = 0x37 猜测应该是不断从v27中取值 还原算法时v27 就是这个下面这个数组

 {0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB, 0x0F, 0xD9, 0x88, 0xEE, 0x9A,
0xE9, 0x5A};

在这里插入图片描述
继续S 往下走 0x38 就是传入固定字符串的第一个字符 80306f4370b39fd5630ad0529f77adb6
在这里插入图片描述

r0 = 0x66 是从拼接的字符串中取出的第一个

r0=0x66 r1=0x0 r2=0x37 r3=0x1 r4=0x38

R0 = R0 ^ R2 = 0x51

R0 = R0 ^ R4 = 0x69
在这里插入图片描述
在这里插入图片描述

按照汇编 分析猜测 后面就是 将前面异或的结果 加上R2 0x37 然后在 R2 = R2 ^ R0

*(v5 - 1) = md5_init_key_index ^ *(_BYTE *)(key + v17);// EOR.W R2, R2, R1    STRB.W R2, [R10,#-1]

根据ida 上伪代码 结和汇编 大致可知 这部分是每执行一次do while 就修改一次v5的索引对应的值 一直循环到循环到v5数据长度,所以结合伪代码 用C还原 sign 的算法 如下

 void encryption(char *data) {
     uint32_t v1, v2, v3 = 0;
     unsigned char init_key[60] = {0x37, 0x92, 0x44, 0x68, 0xA5, 0x3D, 0xCC, 0x7F, 0xBB,    0x0F, 0xD9, 0x88, 0xEE, 0x9A,
                                   0xE9, 0x5A};
     char *sign = "80306f4370b39fd5630ad0529f77adb6";
     int data_length = strlen(data);
     unsigned char md5_init_key = 0;
     unsigned char sign_key = 0;
     do {
         v1 = v3 & 0xF;
         v2 = v3 & 7;
         v3 = v3 + 1;
         sign_key = sign[v2];
         md5_init_key = init_key[v1];
         md5_init_key = md5_init_key ^ ((md5_init_key ^ *data ^sign_key) + md5_init_key);
         *data = md5_init_key ^ sign_key;
         data++;
     } while (v3 != data_length);
 }

用python 来调用C代码 跟unidbg 黑盒调用结果比对如下
在这里插入图片描述
总结: 某东的sign有三个算法 这边只还原了其中比较简单的 sv =111 的这种 。还原这部分算法也花的时间挺多的,结合unidbg 来分析算法 真的方便。

合作联系加Q 1452142503

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值