当前位置: 首页 > news >正文

固定参数-以京东sign逆向为例

前言
在逆向过程中,需要结合frida或unidbg,对整个算法进行一步步的分析,有时候我们分析完某一部分,想要继续往下分析时,需要重新发起请求,这时候的参数往往都已经改变了,这样会打断我们的节奏,影响效率。此外,有些算法除了我们外部传进去的参数外,还有一些其他的参数参与了加密,比如时间戳,随机数,一旦这些参与了算法,那么即使每次的传入参数不变,加密的结果还是会变。

外部输入参数的固定
frida
在京东app的hook中,我们选择编写一个函数,能够固定的调用Java层的native函数。

var bptr = Module.findBaseAddress('libjdbitmapkit.so')
console.log(bptr);function hook_12ECC() {Interceptor.attach(bptr.add(0x12ECC+1), {onEnter: function(args) {this.arg0 = args[0];this.arg3 = args[3];console.log(args[0], args[1], args[2], args[3], args[4]);dump("arg0-ptr0", args[0].readPointer());dump("arg0-ptr1", args[0]);dump("arg0-ptr1", args[0].add(4).readPointer(), 64);console.log("arg1", args[1].readCString());// dump("arg2", args[2]);console.log("arg3", args[3].readCString(parseInt(args[4])));},onLeave: function(){console.log("arg0-ret", this.arg0);dump("ret-arg0-ptr1", this.arg0);// dump("ret-arg0-ptr2", this.arg0.add(8).readPointer());dump("12ecc-ret-arg2", this.arg3)}})
}function callBitmapkitUtils() {var BitmapkitUtils = Java.use('com.jingdong.common.utils.BitmapkitUtils');var currentApplication = Java.use("android.app.ActivityThread").currentApplication();var context = currentApplication.getApplicationContext();var b = 'clientImage';var c = '{"moduleParams":{"18":"1565611060638","19":"1565229712150","25":"1567478504636","27":"1602488415048","28":"1631069159956","30":"1567404005627","32":"1567997588476","34":"1593508185597","35":"1568708316462","37":"1630293538664","42":"1623741761542","44":"1569247647090","46":"1588839806224","47":"1571295610042","61":"1582091758495","70":"1585279774645","74":"1586781606615"}}';var d = 'd5a585639f505b18';var e = 'android';var f = '10.2.0';var res = BitmapkitUtils.getSignFromJni(context, b, c, d, e, f);console.log('res: ', res);
}Java.perform(function() {hook_12ECC();
})

然后启动frida之后,可以在shell中输入callBitmapkitUtils()来调用函数。

这样一来不像在手机上滑动页面、点击页面那样,有时会有多个请求发出,会多次调用加密的方法。这样的好处是,再也不用在手机上操作了,而且请求的内容和个数是可控的。不过我们注意到京东的参数里有st和sv这两个参数,这可不是由我们传入的,属于内部输入参数,接下来我们要固定它们。

unidbg
对于unidbg而言,在我们编写代码的时候,一般都是固定输入的

内部输入参数的固定
frida
对于内部输入参数而言,可能有时间戳,随机数,常量(数字或字符),其中前2个是会改变的,这会影响逆向分析,所以需要固定这两个参数。时间戳一般是调用libc.so的gettimeofday函数,随机数则是调用libc.so的lrand48或srand48

function hook_libc(){var ptr_t = Module.findExportByName("libc.so", "gettimeofday");Interceptor.attach(ptr_t, {onEnter: function(args){this.arg0 = args[0];},onLeave: function() {this.arg0.writeU32(1639393559);this.arg0.add(4).writeU32(0);}});Interceptor.attach(Module.findExportByName("libc.so" , "lrand48"), {onEnter: function(args) {},onLeave:function(retval){retval.replace(1);}});
}
Java.perform(function() {hook_libc();hook_12ECC();
})

这样一来,即使多次运行,st和sv也不会改变,有利于我们的分析。

不过,固定时间戳有个问题,其他函数在获取时间戳的时候发现不对,可能会导致frida环境崩溃,所以我们希望只在调用的时候固定时间戳。所以我们更新如下代码

var logvar = 0;function callBitmapkitUtils() {var BitmapkitUtils = Java.use('com.jingdong.common.utils.BitmapkitUtils');var currentApplication = Java.use("android.app.ActivityThread").currentApplication();var context = currentApplication.getApplicationContext();var b = 'clientImage';var c = '{"moduleParams":{"18":"1565611060638","19":"1565229712150","25":"1567478504636","27":"1602488415048","28":"1631069159956","30":"1567404005627","32":"1567997588476","34":"1593508185597","35":"1568708316462","37":"1630293538664","42":"1623741761542","44":"1569247647090","46":"1588839806224","47":"1571295610042","61":"1582091758495","70":"1585279774645","74":"1586781606615"}}';var d = 'd5a585639f505b18';var e = 'android';var f = '10.2.0';logvar = 1;var res = BitmapkitUtils.getSignFromJni(context, b, c, d, e, f);logvar = 0;console.log('res: ', res);
}function hook_libc(){var ptr_t = Module.findExportByName("libc.so", "gettimeofday");Interceptor.attach(ptr_t, {onEnter: function(args){this.arg0 = args[0];},onLeave: function() {if (logvar) {this.arg0.writeU32(1639393559);this.arg0.add(4).writeU32(0);}}});Interceptor.attach(Module.findExportByName("libc.so" , "lrand48"), {onEnter: function(args) {},onLeave:function(retval){if (logvar){retval.replace(1);}}});
}

这样一来,只有在logvar值为1时才会固定参数。而logvar的默认值为0,只有在调用callBitmapkitUtils方法的时候才会改为1,调用完成后又会改为0。

unidbg
对于unidbg,我们运行多次后会发现,时间戳st会变,sv一直是111,好像和frida上的表现不一样,难道出了什么问题?

其实如果研究了京东libjdbitmapkit.so就会发现sv的后两位都是随机数余3。

而在unidbg对libc.so的随机数生成的实现中,种子是固定的(我猜的,没深究源码),导致生成的随机数的顺序是固定的,继而导致余数是固定的。

public void hook_libc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {int old = ctx.getIntArg(0);System.out.println("Origin rand:" + old);ctx.setR0(1);}});hookZz.wrap(module.findSymbolByName("gettimeofday"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.getR0Pointer();ctx.push(pointer);}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.pop();pointer.setLong(0, 1639388888);pointer.setLong(4, 0);}});
}
public static void main(String[] args) {JingDong test = new JingDong();test.hook_libc();test.callSign();
}

可以看到,时间戳已经被固定下来了,而打印出来的两个随机数,他们的除以3的余数都为1,这也说明了为什么固定参数之前sv的值一直是111。

unidbg完整代码
package com.jingdong;import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
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.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger;
import com.github.unidbg.memory.Memory;
import com.sun.jna.Pointer;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.ParsingException;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.cert.X509Certificate;public class JingDong extends AbstractJni {private final AndroidEmulator emulator;private final VM vm;private final Module module;public static String pkgName = "com.jingdong.app.mall";public static String apkPath = "unidbg-android/src/test/java/com/jingdong/jingdong9.2.2.apk";public static String soPath = "unidbg-android/src/test/java/com/jingdong/libjdbitmapkit.so";private static final String APK_PATH = "/data/app/com.jingdong.app.mall.apk";JingDong() {emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();final Memory memory = emulator.getMemory();memory.setLibraryResolver(new AndroidResolver(23));vm = emulator.createDalvikVM(new File(apkPath));DalvikModule dm = vm.loadLibrary(new File(soPath), false);vm.setJni(this);vm.setVerbose(true);dm.callJNI_OnLoad(emulator);module = dm.getModule();}@Overridepublic DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {switch (signature) {case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": {return vm.resolveClass("android/app/Activity", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null);}}return super.getStaticObjectField(vm, dvmClass, signature);}@Overridepublic DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {switch (signature) {case "android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;": {return new StringObject(vm, APK_PATH);}}return super.getObjectField(vm, dvmObject, signature);}@Overridepublic DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature) {case "com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B": {StringObject apkPath = varArg.getObjectArg(0);StringObject directory = varArg.getObjectArg(1);StringObject filename = varArg.getObjectArg(2);if (APK_PATH.equals(apkPath.getValue()) &&"META-INF/".equals(directory.getValue()) &&".RSA".equals(filename.getValue())) {byte[] data = vm.unzip("META-INF/JINGDONG.RSA");return new ByteArray(vm, data);}}case "com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B": {DvmObject<?> obj = varArg.getObjectArg(0);byte[] bytes = objectToBytes(obj.getValue());return new ByteArray(vm, bytes);}}return super.callStaticObjectMethod(vm ,dvmClass, signature, varArg);}@Overridepublic DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature) {case "sun/security/pkcs/PKCS7-><init>([B)V": {ByteArray array = varArg.getObjectArg(0);try {return vm.resolveClass("sun/security/pkcs/PKCS7").newObject(new PKCS7(array.getValue()));} catch (ParsingException e) {throw new IllegalStateException(e);}}}return super.newObject(vm, dvmClass, signature, varArg);}@Overridepublic DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {switch (signature) {case "sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;": {PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();X509Certificate[] certificates = pkcs7.getCertificates();return ProxyDvmObject.createObject(vm, certificates);}}return super.callObjectMethod(vm, dvmObject, signature, varArg);}@Overridepublic DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {switch (signature) {case "java/lang/StringBuffer-><init>()V": {return vm.resolveClass("java/lang/StringBuffer").newObject(new StringBuffer());}case "java/lang/Integer-><init>(I)V": {return DvmInteger.valueOf(vm, vaList.getIntArg(0));}}return super.newObjectV(vm, dvmClass, signature, vaList);}@Overridepublic DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {switch (signature) {case "java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;": {StringBuffer buffer = (StringBuffer) dvmObject.getValue();StringObject str = vaList.getObjectArg(0);buffer.append(str.getValue());return dvmObject;}case "java/lang/Integer->toString()Ljava/lang/String;": {return new StringObject(vm, ((Integer)dvmObject.getValue()).toString());}case "java/lang/StringBuffer->toString()Ljava/lang/String;": {return new StringObject(vm, ((StringBuffer)dvmObject.getValue()).toString());}}return super.callObjectMethodV(vm, dvmObject, signature, vaList);}private static byte[] objectToBytes(Object obj) {try {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(obj);oos.flush();byte[] array = baos.toByteArray();oos.close();baos.close();return array;} catch (IOException e) {throw new IllegalStateException(e);}}public void hook_libc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {int old = ctx.getIntArg(0);System.out.println("Origin rand:" + old);ctx.setR0(1);}});hookZz.wrap(module.findSymbolByName("gettimeofday"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.getR0Pointer();ctx.push(pointer);}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.pop();pointer.setLong(0, 1639388888);pointer.setLong(4, 0);}});}public void callSign() {DvmClass cBitmapkitUtils = vm.resolveClass("com/jingdong/common/utils/BitmapkitUtils");StringObject ret = cBitmapkitUtils.callStaticJniMethodObject(emulator, "getSignFromJni()(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",vm.resolveClass("android/content/Context").newObject(null),"clientImage","{\"moduleParams\":{\"18\":\"1565611060638\",\"19\":\"1565229712150\",\"25\":\"1567478504636\",\"27\":\"1602488415048\",\"28\":\"1631069159956\",\"30\":\"1567404005627\",\"32\":\"1567997588476\",\"34\":\"1593508185597\",\"35\":\"1568708316462\",\"37\":\"1630293538664\",\"42\":\"1623741761542\",\"44\":\"1569247647090\",\"46\":\"1588839806224\",\"47\":\"1571295610042\",\"61\":\"1582091758495\",\"70\":\"1585279774645\",\"74\":\"1586781606615\"}}","d5a585639f505b18","android","10.2.0");System.out.println(ret.getValue());}public static void main(String[] args) {JingDong test = new JingDong();test.hook_libc();test.callSign();}
}

关于京东加密
其实京东app有3套加密方案,会根据随机数不同来选择不同的方案,而unidbg生成的随机数和京东cv的生成方案导致sv一直是固定的,从而一直调用其中一套方案。我们在逆向的时候,其实只需要逆向出其中一套方案即可,那个简单选哪个,如果刚好是调用的那套方案的话那还好。如果不是,一时之间又不知道怎么处理的话就有点倒霉了。
我将这个解密方法封装成api了,直接调用api可以解密,很方便了
技术交流QQ 408737515

http://www.lryc.cn/news/151590.html

相关文章:

  • 在macOS 上执行sed命令报错问题
  • ARP欺骗
  • pip切换下载源(多种国内源)
  • ARM Cortex-M 的 SP
  • 【原创】鲲鹏ARM构架openEuler操作系统安装Oracle 19c
  • k8s之存储篇---数据卷-挂载
  • 无涯教程-JavaScript - TDIST函数
  • IP子网的划分
  • 弹性盒子的使用
  • 软件测试/测试开发丨Selenium 网页frame与多窗口处理
  • MySQL高阶语句(三)
  • 链表OJ练习(2)
  • ssh常用操作
  • 从AD迁移至AAD,看体外诊断领军企业如何用网络准入方案提升内网安全基线
  • Flutter系列文章-Flutter在实际业务中的应用
  • FPGA | Verilog仿真VHDL文件
  • 微服务--Gatway:网关
  • Django传递dataframe对象到前端网页
  • iOS swift5 弹出提示文字(停留1~2s)XHToastSwift
  • Spring Bean 的生命周期,如何被管理的
  • MATLAB算法实战应用案例精讲-【概念篇】量子机器学习
  • 【kubernetes】Argo Rollouts -- k8s下的自动化蓝绿部署
  • vue Cesium接入在线地图
  • OBS Studio 30.0 承诺在 Linux 上支持英特尔 QSV,为 DeckLink 提供 HDR 回放功能
  • springboot整合SpringSecurity
  • 最近在搭建ELK日志平台时,logstash报错JSON parse error
  • 某次护网红队getshell的经历
  • C#实现日期选择器、显示当地时间、跑马灯等功能
  • 如何让看书变听书?
  • pytorch异常——loss异常,不断增大,并且loss出现inf