
在吾爱破解上看到个合适的案例,学习一下
初始化
先搭一个 unidbg 的架子
package com.md5;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.AbstractJni;import com.github.unidbg.linux.android.dvm.DalvikModule;import com.github.unidbg.linux.android.dvm.DvmClass;import com.github.unidbg.linux.android.dvm.VM;import com.github.unidbg.memory.Memory;
import java.io.File;
//必须继承AbstractJnipublic class modMd5 extends AbstractJni { public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用 public static Memory memory; public static VM vm; public static DvmClass MainActivity; public static Module module; // 1 构造方法--》用来初始化 public modMd5(){ // 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位 // 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的 // 这个名字可以随便写,一般写成app的包名 以后可能会动 emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("").build();
// 2.获取内存对象(可以操作内存) memory = emulator.getMemory();
//3.设置安卓sdk版本(只支持19、23) memory.setLibraryResolver(new AndroidResolver(23));
// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动 vm = emulator.createDalvikVM(new File("apks/md5/md5-release.apk")); vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni vm.setVerbose(true); //是否展示调用过程的细节
// 5.加载so文件 DalvikModule dm = vm.loadLibrary("native-lib", false); // 以后会动 dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册
MainActivity = vm.resolveClass("com.littleq.cryptography.md5.MainActivity"); // 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。 module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件 }
//2 sign 成员方法--》主要用来解密 public void sign(){ String str = "123456"; String result = MainActivity.newObject(null).callJniMethodObject(emulator,"sign(Ljava/lang/String;)Ljava/lang/String;",str).getValue().toString(); System.out.println(result); }
public void hookConsoleByDebugger(){// emulator.attach().addBreakPoint(module.base+0xA3C); emulator.attach().addBreakPoint(module.base+0x8D8); }
// 3 main方法---》右键直接运行 public static void main(String[] args) { modMd5 md5=new modMd5(); md5.hookConsoleByDebugger(); md5.sign(); }}直接运行就可以出结果,当然,我是来学习算法分析的
分析
接着打开 ida,改一下类型,这样好看一点

首先通过 GetStringUTFChars 得到 java 层的字符串,转成 c 语言字符串,得到输入的值给 v4
接着分配内存空间,给 v6,将 v4 传给 sub_A3C 加密后给 v6,最后返回 v6 的值
所以,毫无疑问, sub_A3C 就是加密函数
进来之后,就看到了 md5 的模数值,但是得到的结果并不是标准 md5 加密的结果,说明被魔改了,接下来就来看看是哪儿进行了魔改

首先需要直到一个 md5 函数可能魔改哪些地方?
- 初始化常量
- K 表
- 非线性函数
- 常量转换 首先看一下初始化常量,正常的初始化常量是:
context->state[0] = 0x67452301;context->state[1] = 0xEFCDAB89;context->state[2] = 0x98BADCFE;context->state[3] = 0x10325476;由上图可知,初始化常量它并没有改,那就只能看其他的部分了
第一轮魔改
首先先来看第一轮运算,我把变量名改了一下,这样我看得清楚一点

可以很清楚的看到非线性函数改了,第一轮应该是 FF 函数,他的非线性函数是: (b & c) | ((~b) & d) 被修改成了 (d & c) | ((~d) & b),但是这样不太好看,我选择在 unidbg 中打印寄存器的值
经过分析汇编,我知道了:
A对应着W26寄存器B对应着W25寄存器C对应着W24寄存器D对应着W23寄存器 在 unidbg 中对日志进行打印
public void hookDebugger(){ long baseAddr = module.base; System.out.printf("SO基地址: 0x%x%n", baseAddr); emulator.attach().addBreakPoint(module.base + 0xBBC, new BreakPointCallback() { @Override public boolean onHit(Emulator<?> emulator, long address) { int a = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X26).intValue(); int b = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X25).intValue(); int c = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X24).intValue(); int d = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X23).intValue(); System.out.println("===魔改md5第一轮运算结果==="); System.out.printf("a: 0x%08x, b: 0x%08x, c: 0x%08x, d: 0x%08x\n", a, b, c, d); return true; } });}得到结果如下:

在上面我已经知道非线性函数给改了,我也跟着他改一下,发现结果并不对
那应该是其他地方也改了,比如说,K 表

v14 看到上面赋值是在 0x12D8 的位置,第一次取需要 v14-4 即 0x12D8-0x10=0x12C8 的位置
从 0x12C8 的位置,开始找,k 表果然被魔改了

最后撸下来的 k 表就是这个:
self.T = [ # Round 1 0x8A51407D, 0x6A88995D, 0xFD7025F4, 0xA7553036, 0x489E15C1, 0xF5CDB84B, 0xC0FFBCF6, 0x253F7D7E, 0xE93FD535, 0xD6CD6448, 0x01220AE4, 0xD806D023, 0xE84E6EA9, 0x230135D8, 0xC27AE834, 0xF5292BF4, # Round 2 0x46711AC1, 0xA90A840A, 0xFD1BBEF0, 0x687810E5, 0x8C37FC1B, 0xFFFD6EC6, 0x8867BEAC, 0x6C96FED4, 0xFDBF77AC, 0xA59C8134, 0x4AC99BE5, 0xF66D568A, 0xBF80B2C1, 0x277D05E4, 0xEA2C8E1F, 0xD58FA982, # Round 3 0x03661ADB, # 57023195 0xD93BE6CA, # -650385718 0xE7585F52, # -413638830 0x20C23A76, # 549599862 0xC3F22CE1, # -1007538975 0xF47FB4D2, # -192957230 0x4442B612, # 1145222674 0xAABC73EB, # -1430490133 0xFCC24453, # -54377389 0x66657006, # 1717923846 0x8E1BE7C3, # -1910773821 0xFFF5BB27, # -672985 0x867B8079, # -2038726535 0x6EA336BB, # 1856190139 0xFE09B282, # -32918910 0xA3E07FDA, # -1545568294 # Round 4 0x4CF3A208, # 1291035144 0xF708037E, # -150469762 0xBDFDD143, # -1107439293 0x29B9C389, # 700040073 0xEB1494A7, # -350972761 0xD44DA632, # -733108686 0x05AA195E, # 95033694 0xDA6CA20A, # -630414838 0xE65DAC20, # -430068704 0x1E8296E0, # 511874784 0xC5658374, # -983202956 0xF3D1564B, # -204384693 0x4212F2E5, # 1108538085 0xAC6AF723, # -1402276061 0xFC63B7E7, # -60573721 0x6450C165, # 1683013989]k 表替换之后,结果就正确了

但是并没有完全对,第一轮对了
第二轮魔改
首先看一下第二轮的代码

一来就看见非线性函数给改了
标准的非线性函数是: (B & D) | (C & ~D),而这儿魔改之后就变成了 (D & B) | (C & ~B)
看一下魔改非线性函数之后得到的结果

接着,我去 unidbg 打印一下魔改之后的 A,B,C,D 的值
emulator.attach().addBreakPoint(module.base + 0xC94, new BreakPointCallback() { @Override public boolean onHit(Emulator<?> emulator, long address) { int a = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X26).intValue(); int b = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X25).intValue(); int c = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X24).intValue(); int d = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X23).intValue(); System.out.println("===魔改md5第二轮运算结果==="); System.out.printf("a: 0x%08x, b: 0x%08x, c: 0x%08x, d: 0x%08x\n", a, b, c, d); return true; }});打印结果:
===魔改md5第二轮运算结果===a: 0xa586a548, b: 0x021d429d, c: 0x63694d00, d: 0x251515d5一致,接着看第三轮运算的魔改
第三轮魔改
先来看一下在 ida 中第三轮魔改的代码

发现并没有魔改非线性函数,他左移的位数(常量转换)也是对的,貌似值魔改了 k 表对应的值
到 unidbg 中打印一下:
emulator.attach().addBreakPoint(module.base + 0xEE4, new BreakPointCallback() { @Override public boolean onHit(Emulator<?> emulator, long address) { int a = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_W17).intValue(); int b = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_W23).intValue(); int c = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_W7).intValue(); int d = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_W24).intValue(); System.out.println("===魔改md5第三轮运算结果==="); System.out.printf("a: 0x%08x, b: 0x%08x, c: 0x%08x, d: 0x%08x\n", a, b, c, d); return true; }});得到的值:

到标准算法改了过后的算法中打印一下

一致,接着就需要看第四轮魔改算法了
第四轮魔改算法
看一下魔改的代码呢

发现他是魔改了非线性函数的逻辑,左移位数也是对的
原来的逻辑是: C ^ (B | ~D) 经过修改之后变成: (D | ~B) ^ C
替换进去
正常魔改的结果是:

经过我算法魔改之后的结果:

刚好一致,unidbg 都不用验证了
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 216 天
部分内容可能已过时
评论
还没有评论,来写第一条吧。