meili说mw-sign字段unidbg逆向
Java层
com.mogujie.mwpsdk.valve.RequestSignValve.a
com.mogujie.mwpsdk.valve.RequestSignValve.a
com.mogujie.mwpsdk.security.Signer.a
这是个interface,找它的实现,搜索implements Signer
com.mogujie.mwpsdk.security.SignV1_2.a
com.mogujie.token.UrlTokenMaker.generateNewUrlTokenNative
最后是在libtoken.so
里面
hook看看输入输出
android hooking watch class_method com.mogujie.token.UrlTokenMaker.generateNewUrlTokenNative --dump-args --dump-return
输入的第二个字符串最后拼接了个32位字符串,分析了下是post的data参数的md5。
unidbg实现
public class Meilishuo extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.meilishuo";
public static String apkPath = "unidbg-android/src/test/java/com/meilishuo/meilishuo1071.apk";
public static String soName = "token";
public Meilishuo() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary(soName, true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
return 64;
}
}
return super.getStaticIntField(vm, dvmClass, signature);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/content/pm/Signature->getPublicKey()Ljava/security/PublicKey;": {
return vm.resolveClass("java/security/interfaces/RSAKey").newObject(null);
}
case "java/security/interfaces/RSAKey->getModulus()Ljava/math/BigInteger;": {
return vm.resolveClass("java/math/BigInteger").newObject(null);
}
case "java/math/BigInteger->toString(I)Ljava/lang/String;": {
String str1 = "bf62993ee6167065c274c37ed192f94bcb8ed0ead720246d8c17a1aa3218882aa2cc8abf5f5bb2e2fc0a590ee6cfe9a3c57c2c87f00c4070a8927ce1ce8b9089158d5ca8c2fae2eaf2c2bd1cf7bdf7b4835b01758c9fea39c54345de02a49c7ef94f02fdead461491212520c2bdc6f876c5341d8e24845bcb5808c0573de5e4b";
return new StringObject(vm, str1);
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
public void calc_sign() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
list.add(vm.addLocalObject(new StringObject(vm, "d569bb5990c698efda6b5d8b653fb696")));
list.add(vm.addLocalObject(new StringObject(vm, "2022-03-03&everhu")));
module.callFunction(emulator, 0x2145, list.toArray());
}
public static void main(String[] args) {
Meilishuo test = new Meilishuo();
test.calc_sign();
}
}
在补环境的时候,需要返回一个字符串,可以ida先看看代码。
可以看到sub_1838
的返回值就是需要的字符串。可以frida hook一下看看返回值。
不过有个问题,只有当byte_5050[0]
为0时,才会进入调用sub_1838
。
一种方式是在byte_5050
还没被赋值时进行hook,也就是加密函数第一次被调用时。尝试frida hook一下。
function dump(name, addr, length) {
console.log("======================== " + name + " ============");
console.log(hexdump(addr, {length:length||32}));
}
function hook() {
var bptr = Module.findBaseAddress("libtoken.so");
Interceptor.attach(bptr.add(0x2145), {
onEnter: function(args) {
console.log("call sub_2144");
},
onLeave: function(retval){}
})
Interceptor.attach(bptr.add(0x1839), {
onEnter: function(args) {
console.log("call sub_1838");
},
onLeave: function(retval){
console.log(retval);
var env = Java.vm.tryGetEnv();
var ret = env.getStringUtfChars(retval);
console.log("ret-1838", ret.readCString());
}
})
}
Java.perform(function(){
hook();
})
但是这个代码有个问题,在attach模式下,byte_5050
早已被赋值,sub_1838
不会被调用。在spawn模式下,libtoken.so
还没被加载到内存中,导致找不到so。单纯的加个timeout的话不好控制,所以需要在libtoken.so
刚加载的时候进行hook。
Java.perform(function() {
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if (android_dlopen_ext != null) {
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
this.ok = false;
var soName = args[0].readCString();
if (soName.indexOf("libtoken.so") !== -1) {
this.ok = true;
}
},
onLeave: function (retval) {
if (this.ok) {
hook(); // hook after load
}
}
});
}
})
另一种方式就是每次调用加密函数时,都先把byte_5050
的值清空,这样一来,即使是attach
模式下,也会调用sub_1838
。
function hook() {
var bptr = Module.findBaseAddress("libtoken.so");
Interceptor.attach(bptr.add(0x2145), {
onEnter: function(args) {
console.log("call sub_2144");
Memory.writeByteArray(bptr.add(0x5050), [0,0,0,0]); // clear byte_5050
},
onLeave: function(retval){}
})
Interceptor.attach(bptr.add(0x1839), {
onEnter: function(args) {
console.log("call sub_1838");
},
onLeave: function(retval){
console.log(retval);
var env = Java.vm.tryGetEnv();
var ret = env.getStringUtfChars(retval);
console.log("ret-1838", ret.readCString());
}
})
}
当然,还有另外一种方式,那就是老老实实的补环境,让unidbg把结果算出来。
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/content/pm/Signature->getPublicKey()Ljava/security/PublicKey;": {
Signature sig = (Signature) dvmObject;
System.out.println("sig: " + sig.toCharsString());
byte[] bytes = sig.toByteArray();
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(bytes);
X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
PublicKey publicKey = cert.getPublicKey();
return vm.resolveClass("java/security/interfaces/RSAKey").newObject(publicKey);
} catch (CertificateException e) {
e.printStackTrace();
}
}
case "java/security/interfaces/RSAKey->getModulus()Ljava/math/BigInteger;": {
RSAKey key = (RSAKey) dvmObject.getValue();
BigInteger integer = key.getModulus();
return vm.resolveClass("java/math/BigInteger").newObject(integer);
}
case "java/math/BigInteger->toString(I)Ljava/lang/String;": {
BigInteger integer = (BigInteger) dvmObject.getValue();
int rdx = varArg.getIntArg(0);
String str2 = integer.toString(rdx);
System.out.println(str2);
// String str1 = "bf62993ee6167065c274c37ed192f94bcb8ed0ead720246d8c17a1aa3218882aa2cc8abf5f5bb2e2fc0a590ee6cfe9a3c57c2c87f00c4070a8927ce1ce8b9089158d5ca8c2fae2eaf2c2bd1cf7bdf7b4835b01758c9fea39c54345de02a49c7ef94f02fdead461491212520c2bdc6f876c5341d8e24845bcb5808c0573de5e4b";
return new StringObject(vm, str2);
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
还有一种方式,就是自己把公钥算出来再返回。
结果出来了,不过unidbg每次运行结果都不一样,说明可能有随机数的参与。
固定随机数
public void hook_libc() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
ctx.setR0(1);
}
});
hookZz.wrap(module.findSymbolByName("srand48"), new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
ctx.setR0(1);
}
});
}
public static void main(String[] args) {
Meilishuo test = new Meilishuo();
test.hook_libc();
test.calc_sign();
}
多次调用,结果已经不再变化。
unidbg逆向
跳转到0x2145
sub_1F24
sub_1D28
主要代码都在sub_1D28
里面了。
看看sub_1B3C
MD5的init函数,sub_1B64
对应update函数,sub_1BE8
对应final函数。
hook看看输入。
public void hook_md5() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.base + 0x1B65, new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer input = ctx.getPointerArg(1);
byte[] inputHex = input.getByteArray(0, ctx.getR2Int());
Inspector.inspect(inputHex, "Input");
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
}
});
hookZz.wrap(module.base + 0x1BE9, new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer output = ctx.getR1Pointer();
ctx.push(output);
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer output = ctx.pop();
byte[] outputHex = output.getByteArray(0, 16);
Inspector.inspect(outputHex, "Output");
}
});
}
public static void main(String[] args) {
Meilishuo test = new Meilishuo();
test.hook_libc();
test.hook_md5();
test.calc_sign();
}
打印了挺多次。不过有的是input了3次,然后打印1次output;有的却是input了6次,才打印1次output。所以需要再分析下代码。
当sub_1BEB
的a3
为0时,直接出结果。当为1时,会把MD5的结果倒序再做一次MD5。
输入是unidbg补函数时的字符串,应该是apk签名。
第一次输入是随机数(0001)+输入参数的第一个字符串+第一次MD5的结果。然后MD5的结果倒序再做一次MD5。
输入是之前MD5的结果+输入参数的第二个字符串。
现在分析签名的构造。
f9ae09999f83d5ba
f 9 ae0 9 99 9 f8 3 d5ba
f ae0 99 f8 d5ba
9 9 9 3
f ae0 99 f8 d5ba
可以看出是最后一次MD5的前面几位。
9993
则是对随机数0001
做了个映射变换。
根据代码实现一下即可。
unidbg代码
public class Meilishuo extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.meilishuo";
public static String apkPath = "unidbg-android/src/test/java/com/meilishuo/meilishuo1071.apk";
public static String soName = "token";
public Meilishuo() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary(soName, true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
return 64;
}
}
return super.getStaticIntField(vm, dvmClass, signature);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/content/pm/Signature->getPublicKey()Ljava/security/PublicKey;": {
return vm.resolveClass("java/security/interfaces/RSAKey").newObject(null);
}
case "java/security/interfaces/RSAKey->getModulus()Ljava/math/BigInteger;": {
return vm.resolveClass("java/math/BigInteger").newObject(null);
}
case "java/math/BigInteger->toString(I)Ljava/lang/String;": {
String str1 = "bf62993ee6167065c274c37ed192f94bcb8ed0ead720246d8c17a1aa3218882aa2cc8abf5f5bb2e2fc0a590ee6cfe9a3c57c2c87f00c4070a8927ce1ce8b9089158d5ca8c2fae2eaf2c2bd1cf7bdf7b4835b01758c9fea39c54345de02a49c7ef94f02fdead461491212520c2bdc6f876c5341d8e24845bcb5808c0573de5e4b";
return new StringObject(vm, str1);
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
public void hook_libc() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
ctx.setR0(1);
}
});
hookZz.wrap(module.findSymbolByName("srand48"), new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
ctx.setR0(1);
}
});
}
public void hook_md5() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.base + 0x1B65, new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer input = ctx.getPointerArg(1);
byte[] inputHex = input.getByteArray(0, ctx.getR2Int());
Inspector.inspect(inputHex, "Input");
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
}
});
hookZz.wrap(module.base + 0x1BE9, new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer output = ctx.getR1Pointer();
ctx.push(output);
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer output = ctx.pop();
byte[] outputHex = output.getByteArray(0, 16);
Inspector.inspect(outputHex, "Output");
}
});
}
public void calc_sign() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
list.add(vm.addLocalObject(new StringObject(vm, "d569bb5990c698efda6b5d8b653fb696")));
list.add(vm.addLocalObject(new StringObject(vm, "2022-03-03&everhu")));
module.callFunction(emulator, 0x2145, list.toArray());
}
public static void main(String[] args) {
Meilishuo test = new Meilishuo();
test.hook_libc();
test.hook_md5();
test.calc_sign();
}
}