密码学学习记录|aes dfa 练习样本二

密码学学习记录 14 / 15

前言

dfa 攻击相关资料,可参考之前的文章 从黑盒攻击模型到白盒攻击模型
样本来自龙哥星球,需要获取可加星球。上述链接有二维码

unidbg

祖传操作,架子先搭起来

package com.qinless.zhaolianjinrong.v630;

import com.androidFrameWork.QLUtils;
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.*;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

public class HoneyBeeAesTest extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public DvmObject<?> dvmObject;

    public String apkPath = "/Volumes/T7/android/android-file/zhaolianjinrong-6.3.0.apk";

    HoneyBeeAesTest() {
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.zl.fqbao").build();
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(apkPath));
        vm.setJni(this);
        vm.setVerbose(true);

        DalvikModule dm = vm.loadLibrary("honeybee", true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);

        dvmObject = vm.resolveClass("com/mucfc/honeybee/CollectorManager").newObject(null);
    }

    public static void main(String[] args) {
        HoneyBeeAesTest honeyBeeAesTest = new HoneyBeeAesTest();

        honeyBeeAesTest.destroy();
    }

    private void destroy() {
        try {
            emulator.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行成功,发现没啥输出,打开 so 分析一波

导出窗口搜索 java 关键词,根据符号名称可以清楚的看到第一个是加密函数,第二个是初始化函数,下面使用 unidbg 调用一下

得益于符号没去,可以直接看到函数的参数类型

public void callInitialize() {
    dvmObject.callJniMethodObject(
            emulator, "initialize(Landroid/content/Context;)V;",
            vm.resolveClass("android/content/Context").newObject(null)
    );
}

public void callEncryptPhoneNum() {
    DvmObject<?> ret = dvmObject.callJniMethodObject(
            emulator, "encryptPhoneNum(Ljava/lang/String;I)[B;",
            new StringObject(vm, "bacde"), 5
    );
    System.out.println("result : " + QLUtils.bytesToHexString((byte[]) ret.getValue()));
}

代码跑起来,发现结果不太对,有点长,有猫腻,so 分析一波

so

进入 encryptPhoneNum 函数分析

通过 unidbg 的日志可以看出来,最后是调用 SetByteArrayRegion 获取结果的

在这之前调用了 memcpy 函数,拼接 j_mask_phone_number, j_encrypt_phone_number 这两个函数的结果,点进去看一下

j_mask_phone_number

这个函数是一些 hash 算法,貌似跟 aes 没关系,略过

j_encrypt_phone_number

这个函数就很明显了,是 aes 算法

public void consoleDebugger() {
    Debugger debugger = emulator.attach(DebuggerType.CONSOLE);

    debugger.addBreakPoint(module.base + 0x958C);
    debugger.addBreakPoint(module.base + 0x95A6);
}

下面在使用 unidbg console debugger 动态调试看一下,先下两个断点

这两个函数的第二个参数是用来保存返回数据的,所以直接保存 r1 的地址,使用 n 单步执行后,在查看 r1 的数据

可以看到最后的结果是 hash + aes 拼接起来的,所以咱们只需要关注 j_encrypt_phone_number aes 算法即可,下面来分析下

aes

继续分析 j_encrypt_phone_number 函数

这里就是循环调用了,每 16 个字节数据为一组,这也是 aes 的特征,这里虽然能看到 key 但我们不用去管他,毕竟是练习 aes dfa,继续跟下去

这个样本的符号很友好,完全没去除,所以可以直接看到,密钥编排函数,跟加密函数

点进加密函数,这就能看到 aes 的核心逻辑了,是个标准的 aes,咱们就按照龙哥文章里说的逻辑,在箭头这里注入故障值

public int randInt(int min, int max) {
    Random rand = new Random();
    return rand.nextInt((max - min) + 1) + min;
}

public void dfaAttack(final long off) {
    emulator.attach().addBreakPoint(module.base + 0xCF34 + 1, new BreakPointCallback() {
        int count = 0;
        UnidbgPointer pointer;

        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            count += 1;
            RegisterContext registerContext = emulator.getContext();

            pointer = registerContext.getPointerArg(0);

            if (count % 9 == 0) {
                pointer.setByte(off, (byte) randInt(0, 0xff));
            }

            return true;
        }
    });
}

public static void main(String[] args) {
    long[] ints = new long[]{
            1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10,
            11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16
    };

    for (long anInt : ints) {
        HoneyBeeAesTest honeyBeeAesTest = new HoneyBeeAesTest();

        honeyBeeAesTest.dfaAttack(anInt);

        honeyBeeAesTest.callInitialize();
        honeyBeeAesTest.callEncryptPhoneNum();
        honeyBeeAesTest.destroy();
    }
}

同样的逻辑,每个字节注入两次

运行保存所有的错误密文,使用 phoenixAES 跑一下

轮密钥,跟主密钥也是成功计算出来了

最后

这里面有个坑博主没有说,大家自行踩一下

暂无评论
本文作者:
本文链接: https://www.qinless.com/?p=1701
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 qinless 的博客!
100

发表评论

返回顶部