仅供学习研究 。请勿用于非法用途,本人将不承担任何法律责任。
前言
mitmproxy抓包
java分析
定位到CryptoHelper类的名为md5_crypt的native静态方法。
frida hook
脚本如下所示
function hook() {
Java.perform(function() {
var CryptoHelper = Java.use('com.l*****e.safeboxlib.CryptoHelper');
CryptoHelper.md5_crypt.implementation = function (x, y) {
console.log('md5_crypt_x', bytes2str(x));
console.log('md5_crypt_y', y);
var result = this.md5_crypt(x, y);
console.warn('md5_crypt_ret', bytes2str(result));
return result;
};
}
}
我们可以看到,hook的结果和抓包结果一致
so分析
使用lasting-yang的脚本hook_RegisterNatives
脚本地址:https://github.com/lasting-yang/frida_hook_libart
使用开源的cutter到so去一探究竟,我们搜索上图中的偏移0x1a981,来到android_native_md5函数。
经过一番分析,应该是md5加密完之后,还有一个bytesToInt的逻辑。
unidbg
去年的文章里用frida就能搞定了,这次我们用lilac、qinless、qxp等大佬极力推荐的神器unidbg进行辅助分析。
首先是搭建架子
package com.*;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.IOResolver;
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.array.IntArray;
import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class Md5Crypt440 extends AbstractJni {
private final AndroidEmulator emulator;
private final Module module;
private final VM vm;
public String apkPath = "/Users/darbra/Downloads/apk/com.*/temp.apk";
public String soPath2 = "/Users/darbra/Downloads/apk/com.*/lib/armeabi-v7a/libcryptoDD.so";
Md5Crypt440() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.*").build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setVerbose(true);
vm.setJni(this);
DalvikModule dm2 = vm.loadLibrary(new File(soPath2), true);
dm2.callJNI_OnLoad(emulator);
module = dm2.getModule();
}
public void call() {
String methodId = "md5_crypt([BI)[B";
DvmClass SigEntity = vm.resolveClass("com/luckincoffee/safeboxlib/CryptoHelper");
String fakeInput1 = "cid=210101;q=j***=;uid=***";
ByteArray inputByteArray1 = new ByteArray(vm, fakeInput1.getBytes(StandardCharsets.UTF_8));
DvmObject ret = SigEntity.callStaticJniMethodObject(
emulator, methodId,
inputByteArray1,
1
);
byte [] strByte = (byte[]) ret.getValue();
String strString = new String(strByte);
System.out.println("callObject执行结果:"+ strString);
}
public static void main(String[] args) {
Md5Crypt440 getSig = new Md5Crypt440();
getSig.call();
getSig.destroy();
}
private void destroy() {
try {
emulator.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
值得注意的是,这个app不用补任何环境,非常轻松地就运行出了结果。
结果和抓包、frida完全一致。
我们在md5函数打个断点。
public void HookByConsoleDebugger(){
Debugger debugger = emulator.attach();
debugger.addBreakPoint(module.base+0x13E3C);
}
输入mr0,我们可以看到第一个参数就是明文,但不够完整。
接着输入mr0 0x100,后面可以跟的是大小。我们欣喜地发现,之前的那坨明文后面加了个salt值,d**9
参数2,r1=0xef=239,我们去cyberchef看看这个明文长度是否为239
果不其然是的。
接着输入mr2,查看第三个参数。
看情况参数3不出重大意外是buffer,所以我们需要hook它的返回值。在这里我们先记下r2的地址--->>> 0xbffff5d8。
我们使用blr 命令用于在函数返回时设置一个一次性断点,然后c 运行函数,它在返回处断下来。
接着输入刚刚记录下来的 m0xbffff5d8
我们去cyberchef进行验证,完全正确!
md5步骤解决了,接着查看bytesToInt。
我们在0x13924那里进行hook操作
public void hookBytesToInt() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.base + 0x13924 + 1, new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Inspector.inspect(ctx.getR0Pointer().getByteArray(0, 0x10), "参数1");
System.out.println("参数2->>" + ctx.getR1Int());
};
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
System.out.println("返回值->>" + ctx.getR0Int());
}
});
}
我们看一下输出结果了,四个输出值都能对应上最后的sign结果。
其中的正负号处理应该是对应的如下逻辑
我们写个小脚本还原下,与之前的抓包、frida、unidbg都一致,大功告成。
在本人github.com/darbra/sign 有更多的一些思路交流,如果对老师们有所帮助,不甚欣喜。