在 IDA 反编译中,编译器计算出来的 习语 会通过IDA 反序列为 特殊的指令,这个叫 宏指令 这块的内容 也是很重要的内容,因为很多东西都是离不开这部分的内容 进行分析逻辑
**这个 CFADD 指令的 简单解释是 ****返回两个参数相加后的进位标志位 **
直接用当前的 案例进行解释,例如说,在当前案例中 有下述代码块
刚才 已经说了,CFADD 指令就是 当前 两个操作参数 进行相加的操作,那么 其实可以看看 对应的Trace 日志
简单的分析已经得知,w21 实际上就是 输入的明文长度 我们入参输入的是 123456,那么 对应的 w21是0x6
在 伪代码中 显示的是 *8a3 在汇编中(逆向多是以汇编为主) ** lsl #3 那么就是 把原本输入的 w21 左移 << 3
再加上 **w9 **得出最终的结果 **w8 ** 已知 **w9 **是 **0, **那么简单来计算一下 print(6<<3)

对比一下, 符合Trace 出来的日志, 0x30 , 那么现在就又多掌握了一个基本的 宏指令 CDADD
再多讲一个,ROL4 指令 简单解释就是 循环左移
可以记忆一个小细节问题 部分加密算法是 全局贯穿着 循环左移的使用 例如
在 如今 安卓逆向的 行业来说,各种卷王 层出不跌。那么 魔改算法,就是某些卷王提出的一些方案,让保护提升安全性
这里呢 就简单介绍了一下 加密常数表的修改和四个初始化向量的修改,其他的内容,可以关注公众号
加群,后面会有更多的魔改/OLLVM/小众算法的实战内容,文章,视频分享
现在 结合 标准 MD5 加密的代码 去看一看 什么是 加密码表的修改

标准常数表 T
MD5 加密 标准的常数表是 64 个常数,这也是 MD5 加密比较明显的特征点,结合该案例去看一看,就简单结合 Trace 去看一看。
例如 在该案例的 Sub_AD8 函数中 就能发现大量的计算 如果细心一点就能看出来,常数表是 64 个常数,那么实际上 最终的轮计算也是分为 64 调用
这 **64 ** 次调用 可以简单的称之为 **Round 1...4 **
那么 现在就结合 Trace 日志 简单的 看看 每个轮次的 加密的是 T 常量表里面的内容 是否是被修改过的 我们简单的去找 两三个位置吧


任意挑了 几个位置的 **T **表中的 常量 直接去 Trace 里面 搜一下,就很明显,都是存在的,那么其实就可以判断,T 表应该大概率没有被修改过的
修改 T 表的 魔改方式 其实是 频率 很高的,相对来说 技术成本低,就很容易出现在 魔改 MD5 的场景下
要 谈论 魔改之前,还是应该先看一看 标准情况下,四个初始化向量 是什么?
那么 现在知道了 四个初始化的 IV 的值了 ,现在就应该要知道,这四个值 应该是用到哪里,怎么去找? 在这里我 写一个 示意图
那么 就已经 简单知道了 四个初始化的 IV 是用到了 与每一个 消息块计算的过程中,但是 四个初始化的 IV 会变化吗?会变化的!

这个操作 还是非常明显的 在 MD5 加密的末尾 是有一个明显的 四个初始化向量 写回 的操作 ,那么 现在要做的事情就是 根据伪代码去定位疑似写回位置
四个 初始化 IV 写回的位置是在 标准 MD5 加密的末尾,那么 同样 我们也可以去看 该 MD5 伪代码的末尾...............

*a2,a2[1],a[2],a2[3] 这个 很明显是一个入参值,那么就 Hook 简单的看一看

这里呢 要注意一下 在内存中是以小端序的形式体现出来的,a2 的入参是 看前面的 16 个字节 每四个字节分为一组,第一轮次的初始化向量是没问题的

但是 在这里 第二轮次 就已经发现 四个初始化的 IV 是被修改的 已经变了, 先不管这里的地方 继续往下看看。

经过 简单的分析 得知,入参为
经过了 三轮的 MD5 加密 四个 IV 其中 第一轮是正常的,第二轮是被修改的,第三轮是 正常的
在 分析 里面逻辑加密之前呢 我们已经知道 有经过了 三次的 MD5 加密,那么 想一想,最终加密输出的值是一个 32 位的长度,但是有三次 MD5 加密的操作,那么三次的 MD5 肯定不会 平白无故 的出现,那么 现在应该做的事情就是 输出 三次 MD5 加密的结果
上面可能有些是我写的有问题,别问,问就是 看的眼睛花
那么 实际上 就是 发现,第一次加密出来的结果 实际上 就是 第二次加密的时候 使用的 四个初始化 IV 那么 我们应该尝试一下 进行 第一次 MD5 加密
调用 标准的 MD5 之后 发现 结果还是不一样,现在就要思考,为什么会不一样?到底是还有哪里被魔改了? 现在对比一下 标准情况下 加密前的消息体


有点 明显啊 他就取了 前 64 个字节 对应就是 512bit 后面不够 64 个字节的 也没有填充 直接 **delete **
所以 现在就可以 尝试一下 第一次加密了 就按照 原本他的逻辑 取去,但是需要,需要你原本的明文数据就要够 64 个字节 如果不够的话 就会报错
贴一下辅证

现在 开始 讲一下 第二次 MD5

这里 我就简单讲一讲 需要注意点的 就是 第二次加密的时候 4 个初始化的 IV 就是 第一次 MD5 加密的结果
加密数据的内容就是 传输的明文数据 第一次 MD5 是取前 64 位 第二次加密的就是 前 64 位后面的内容 也就是 [64:str_len]
但是 大家看 这个明文数据的数据 是有填充的 看它的 前 64 个字节 有没有发现的 多了一个 02?

这块的 内容 我就直接说了, 就不做过多详细的讲解了,认真看一看 伪代码 就能看出来,实际上 他是有一个判断的 如果长度 判断 256 大于小于 如果 然后会在 **填充 a8 的后面 加入 0x02 或者是 0x03 **
我就不在这里多赘述了,后面会有视频在 B 站上发出,后面如果对这部分感兴趣的 都可以关注一下,第三次的 MD5 就是一个标准的 MD5 第二次 MD5 结果 进行 Base64 编码 然后进行 MD5 加密 直接看一下


package com;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
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.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.github.unidbg.virtualmodule.android.JniGraphics;
import com.github.unidbg.virtualmodule.android.MediaNdkModule;
import com.github.unidbg.virtualmodule.android.SystemProperties;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class 模板 extends AbstractJni implements IOResolver {
private static final String PackName = "APP 包名";
private static final String AppPath = "传入 APP路径";
private static final String[] SoName = {"SO名字或外部路径"};
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
final Memory memory;
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("Load File: " + pathname);
return null;
}
private static LibraryResolver createLibraryResolver() {
return new AndroidResolver(23);
}
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder.for64Bit()
.setProcessName(PackName)
.addBackendFactory(new Unicorn2Factory(false))
.build();
}
模板 () {
emulator = createARMEmulator();
emulator.getSyscallHandler().addIOResolver(this);
memory = emulator.getMemory();
memory.setLibraryResolver(createLibraryResolver());
vm = emulator.createDalvikVM(new File(AppPath));
new AndroidModule(emulator,vm).register(memory);
new MediaNdkModule(emulator,vm).register(memory);
new JniGraphics(emulator,vm).register(memory);
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary(SoName[0], true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
};
public static void main(String[] args) {
模板 action = new 模板();
action.Call();
}
public void Call () {
List<Object> args = new ArrayList<>();
args.add(vm.getJNIEnv());
args.add(0);
args.add(0);
args.add(vm.addLocalObject(new StringObject(vm, "bcae572b84d20c385d6d9d2d7d9e645da29da3c0user/login18953675221WA89qByLlDeaGjmVNzXm/w==")));
args.add(3);
Number result = module.callFunction(emulator, 0x2414, args.toArray());
DvmObject<?> returnObj = vm.getObject(result.intValue());
String encrypted = ((StringObject) returnObj).getValue();
System.out.println("Encrypted String: " + encrypted);
}
}
package com;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
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.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.github.unidbg.virtualmodule.android.JniGraphics;
import com.github.unidbg.virtualmodule.android.MediaNdkModule;
import com.github.unidbg.virtualmodule.android.SystemProperties;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class 模板 extends AbstractJni implements IOResolver {
private static final String PackName = "APP 包名";
private static final String AppPath = "传入 APP路径";
private static final String[] SoName = {"SO名字或外部路径"};
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
final Memory memory;
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("Load File: " + pathname);
return null;
}
private static LibraryResolver createLibraryResolver() {
return new AndroidResolver(23);
}
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder.for64Bit()
.setProcessName(PackName)
.addBackendFactory(new Unicorn2Factory(false))
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!