1713 字
9 分钟
某md5魔改样本学习

在吾爱破解上看到个合适的案例,学习一下

初始化#

先搭一个 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;
//必须继承AbstractJni
public 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-40x12D8-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 都不用验证了

某md5魔改样本学习
https://819229.xyz/posts/某md5魔改样本学习/
作者
0xnonce
发布于
2026-01-26
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...
0/2000

    最后更新于 ,距今已过 216

    部分内容可能已过时