京东sign unidbg逆向
环境
jdk 1.8.0_311
app 9.2.2
unidbg还原
新建包和类
添加代码
package com.jingdong;
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.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 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();
}
public static void main(String[] args) {
JingDong test = new JingDong();
}
}
运行后报错
补上方法
// 错误版本
@Override
public 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/Application").newObject(null);
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
运行失败
说实话,没太看懂什么问题,于是上网搜了下,在Illegal JNI version: 0xffffffff · Issue #315 · zhkl0228/unidbg (github.com)这个unidbg的issue下找到了unidbg作者对京东逆向的实现。此外,还有Unidbg模拟执行大厂so实操教程(二) - 奋飞安全 (91fans.com.cn)这篇文章提到了类似的问题。
@Override
public 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);
}
运行后有新的问题
补上
@Override
public 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);
}
新的问题+1
@Override
public 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);
}
}
}
return super.callStaticObjectMethod(vm ,dvmClass, signature, varArg);
}
新的问题+1
@Override
public DvmObject> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "sun/security/pkcs/PKCS7->([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);
}
这里使用jdk1.8可以直接运行,使用jdk1.16会报sun.security.pkcs.PKCS7
的相关问题,请自己解决(因为我也不知道怎么解决,才会直接换到jdk1.8。。)
新的问题+1
@Override
public 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);
}
新的问题+1
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);
}
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 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.callSign();
}
又有新的问题,继续补环境
@Override
public DvmObject> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/lang/StringBuffer->()V": {
return vm.resolveClass("java/lang/StringBuffer").newObject(new StringBuffer());
}
}
return super.newObjectV(vm, dvmClass, signature, vaList);
}
新的问题+1
@Override
public 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;
}
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
新的问题+1
case "java/lang/Integer->(I)V": {
return DvmInteger.valueOf(vm, vaList.getIntArg(0));
}
新的问题+1
case "java/lang/Integer->toString()Ljava/lang/String;": {
return new StringObject(vm, ((Integer)dvmObject.getValue()).toString());
}
新的问题+1
case "java/lang/StringBuffer->toString()Ljava/lang/String;": {
return new StringObject(vm, ((StringBuffer)dvmObject.getValue()).toString());
}
再次运行
泪目,终于出结果了。
完整代码
package com.jingdong;
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.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 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();
}
@Override
public 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);
}
@Override
public 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);
}
@Override
public 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);
}
@Override
public DvmObject> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "sun/security/pkcs/PKCS7->([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);
}
@Override
public 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);
}
@Override
public DvmObject> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/lang/StringBuffer->()V": {
return vm.resolveClass("java/lang/StringBuffer").newObject(new StringBuffer());
}
case "java/lang/Integer->(I)V": {
return DvmInteger.valueOf(vm, vaList.getIntArg(0));
}
}
return super.newObjectV(vm, dvmClass, signature, vaList);
}
@Override
public 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 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.callSign();
}
}
其他
unidbg逆向就是个补环境的工作,前面我们补环境的时候,其实可以将一些通用的方法在父类中实现,比如对StringBuilder的操作,对Integer的操作,而不是选择在子类中实现,这样就不用每次逆向的时候都要写同样的代码。
具体来说,就是在unidbg-android/src/main/java/com/github/unidbg/linux/android/dvm/AbstractJni.java
文件中添加对应的代码
上面这些是unidbg的作者已经实现的方法,我们只需要在后面补上需要实现的方法即可。当然,保证你的实现的方式具有通用性,不然在逆向新的app的时候出了问题的话一时间很难察觉。
代码仅供把玩。