unidbg 是一个基于 unicorn 的逆向工具,可以黑盒调用安卓和 iOS 中的 so 文件。unidbg 是一个标准的 java 项目。
由于现在的大多数 app 把签名算法已经放到了 so 文件中,所以要想破解签名算法,必须能够破解 so 文件。但是我们知道,C++ 的逆向远比 Java 的逆向要难得多了,所以好多时候是没法破解的,那么这个时候还可以采用 hook 的方法,直接读取程序中算出来的签名,但是这样的话,需要实际运行这个应用,需要模拟器或者真机,效率又不是很高。
unidbg 就是一个很巧妙地解决方案,他不需要直接运行 app,也无需逆向 so 文件,而是通过在 app 中找到对应的 JNI 接口,然后用 unicorn 引擎直接执行这个 so 文件,所以效率也比较高。
案例来自 JXU2QkQyYXBwJTIwdjQuMTYuMA== 对于该 app 而言,是非常适合入门的一个 app,未加固、算法简单、很容易找到 so 的 jni。 先去凯神的 github 上下载https://github.com/zhkl0228/unidbg 下载完毕用 idea 打开,等待 maven 下载完毕。我这里已经创建好 du 的文件。
public class du extends AbstractJni {
//ARM 模拟器
private final ARMEmulator emulator;
//vm
private final VM vm;
//载入的模块
private final Module module;
private final DvmClass TTEncryptUtils;
//初始化
public du() throws IOException {
//创建 app 进程,这里其实可以不用写的,我这里是随便写的,使用 app 本身的进程就可以绕过进程检测
emulator = new AndroidARMEmulator("com.du.du");
Memory memory = emulator.getMemory();
//作者支持 19 和 23 两个 sdk
memory.setLibraryResolver(new AndroidResolver(23));
memory.setCallInitFunction();
//创建 DalvikVM,利用 apk 本身,可以为 null
//如果用 apk 文件加载 so 的话,会自动处理签名方面的 jni,具体可看 AbstractJni,利用 apk 加载的好处,
// vm = emulator.createDalvikVM(new File("src/test/resources/du/du4160.apk"));
我这里没有用到 apk,主要是没有检测其他因素。
vm = emulator.createDalvikVM(null);
//加载 so,使用 armv8-64 速度会快很多,这里是 so 的文件路径,其实也可以利用 apk 自身的。
DalvikModule dm = vm.loadLibrary(new File("src/test/resources/du/libJNIEncrypt.so"), false);
//调用 jni
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
//加载 so 的那个类
TTEncryptUtils = vm.resolveClass("com/duapp/aesjni/AESEncrypt");
}
//关闭模拟器
private void destroy() throws IOException {
emulator.close();
System.out.println("destroy");
}
public static void main(String[] args) throws IOException {
du t = new du();
t.encodeByte();
t.destroy();
}
private String encodeByte() {
//调试
// 这里还支持 gdb 调试,
//emulator.attach(DebuggerType.GDB_SERVER);
//附加调试器
// emulator.attach(DebuggerType.SIMPLE);
// emulator.traceCode();
//这里是打断点,原地址 0x00005028->新地址 0x40005028 新地址需要改成 0x4
// emulator.attach().addBreakPoint(null, 0x40001188);//encode 地址
// emulator.attach().addBreakPoint(null, 0x40000D10);
Number ret = TTEncryptUtils.callStaticJniMethod(emulator, "getByteValues()Ljava/lang/String;");
long hash = ret.intValue() & 0xffffffffL;
StringObject st1 = vm.getObject(hash);
//*这里要处理下字符串
String byteString = st1.getValue();
StringBuilder builder = new StringBuilder(byteString.length());
for (int i = 0; i < byteString.length(); i++) {
if (byteString.charAt(i) == '0') {
builder.append('1');
} else {
builder.append('0');
}
}
//获取 encodeByte 地址
ret = TTEncryptUtils.callStaticJniMethod(emulator, "encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
//传参,这里需要两个字符串,所以就传入两个参数
vm.addLocalObject(new StringObject(vm, "要加密的值")),
vm.addLocalObject(new StringObject(vm, builder.toString())));
//ret 返回的是地址,
hash = ret.intValue() & 0xffffffffL;
//获得其值
StringObject str = vm.getObject(hash);
System.out.println(str.getValue());
return str.getValue();
}
}
上边代码有 jni 的类是哪一个需要知道,就是下面这个类,这个其实是和加载 so 有关系的。
TTEncryptUtils = vm.resolveClass("com/*/aesjni/AESEncrypt");
我们需要逆向 app,这里不细说如何在 app 中寻找加载 so 的类。如下图,encodeByte 是该 app 调用 native 层加密的入口,loadLibrary 是 java 加载 so 的方法,这个类就是上述代码中填写的。
"encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
这里,这是 smali 写法,不补基础,后面跟上需要传的参数,
getByteValues
这个方法是毒获取的一个 01 字符串,并且在 java 层进行了处理,然后再传进encodeByte
里面,encodeByte
这个方法最后获取的其实并不是最终需要的,需要 md5 才是最后的 newSign。可以验证一下下。
启动 java 文件时候注意这个改成自己的平台!!!
VM options: -Djava.library.path=prebuilt/os -Djna.library.path=prebuilt/os
Where os may: linux64, win32, win64, osx64
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.