比亚迪hy SM4 白盒 DFA 攻击

案例:比亚迪hy

SM4密码算法详解:分组长度与加密流程-CSDN博客

https://github.com/py-gmssl/py-gmssl 可以看源码

https://github.com/guojuntang/sm4_dfa dfa攻击

(六)国密SM4算法 - 知乎 (zhihu.com)

分析SO

这里我没看java层,从so的java_开始查看,根据经验可得

Java_com_bangcle_comapiprotect_CheckCodeUtil_checkcode 入口
这里是sm4的算法入口,bcda123fcd4d2019 这里为什么是iv?
bangcle_QSM4_cbc_encrypt(v112, v125, v128, &v789, "bcda123fcd4d2019", 16LL, v135, 131076LL, 1);// bcda123fcd4d2019=iv 

iv 探究

上面代码追踪到如下:

v18 = bangcle_CRYPTO_cbc128_encrypt(p, a3, item_count, bcda123fcd4d2019, &v23, off_142FA0);
进去之后发现就是用来异或的,iv的作用就是这个。 这里我做了patch,所以最后的iv=0000000000000000000000000000000
//这里要分为16个字符长度,每个字符跟bcda123fcd4d2019的相应位置异或。得到sm4加密之前的明文输入,1234得到的是SQWU=>?joh8h><=5

sm4加密探究

进入a6之后,可以看看算法如下,怎么看a6? 这里看看x4的值。或者off_142FA0点进去看看就行


a6=bangcle_WB_QSM4_encrypt(int64 a1, int64 a2, __int64 *a3) 最终的方法

根据前序的知识,可以得出非常标准的算法。

如图,注入位置,后续就开始注入。使用unidbg 代码如下:

package com;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
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.linux.android.dvm.api.SystemService;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import com.sun.jna.Pointer;
import keystone.Keystone;
import keystone.KeystoneArchitecture;
import keystone.KeystoneMode;
import unicorn.Arm64Const;

import java.io.*;
import java.util.ArrayList;
import java.util.Random;

/*
 */
public class bydhy extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("get path:" + pathname);
        if ("/proc/self/maps".equals(pathname) || ("/proc/" + emulator.getPid() + "/maps").equals(pathname)) {
            return FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/resources/byd/bydmpas"), pathname));
        }
        return null;
    }

    bydhy() {
// 创建模拟器实例
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.byd.sea").build();
// 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
// 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        emulator.getSyscallHandler().addIOResolver(this);
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/byd/bydhy.apk"));
// 设置JNI
        vm.setJni(this);
// 打印日志
        vm.setVerbose(true);
        new AndroidModule(emulator, vm).register(memory);
        ;
// 加载目标SO
// DalvikModule dm = vm.loadLibrary("encrypt", true);
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/byd/libencrypt_bydhy.so"), true);
//获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
// 调用JNI OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    ;

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;": {
                return dvmClass.newObject(null);
            }
            case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
                String arg0 = varArg.getObjectArg(0).getValue().toString();
                String arg1 = varArg.getObjectArg(1).getValue().toString();
                System.out.println(arg0 + "====" + arg1);
                if (arg0.equals("ro.serialno")) {
                    return new StringObject(vm, "unknown");
                }
                return new StringObject(vm, "");
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;": {
                return vm.resolveClass("android/app/ContextImpl").newObject(null);
            }
            case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;": {
                DvmClass clazz = vm.resolveClass("android/content/pm/PackageManager");
                return clazz.newObject(signature);
            }
            case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;": {
                StringObject serviceName = varArg.getObjectArg(0);
                assert serviceName != null;
                System.out.println(serviceName.toString());
                return new SystemService(vm, serviceName.getValue());
            }
            case "android/net/wifi/WifiManager->getConnectionInfo()Landroid/net/wifi/WifiInfo;": {
                return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);
            }
            case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;": {
                return new StringObject(vm, "da:c4:13:ef:95:aa");
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {

        switch (signature) {
            case "android/os/Build->MODEL:Ljava/lang/String;":
                return new StringObject(vm, "google");
            case "android/os/Build->MANUFACTURER:Ljava/lang/String;":
                return new StringObject(vm, "google1");
            case "android/os/Build$VERSION->SDK:Ljava/lang/String;":
                return new StringObject(vm, "20.1");
        }
        return super.getStaticObjectField(vm, dvmClass, signature);

    }

    public String checkcode() throws FileNotFoundException {
//        emulator.attach().addBreakPoint(module.base + 0x29E6C); //28轮
//        emulator.attach().addBreakPoint(module.base + 0x29E9C); //29轮
//        emulator.attach().addBreakPoint(module.base + 0x29ECC); //30轮
//        emulator.attach().addBreakPoint(module.base + 0x29efc); //31轮
//        emulator.attach().addBreakPoint(module.base + 0x2580C); //结果下断

//        先把这里nop掉,好分析 不进行nop的值=XsdchC8eiT4MgMiS0PMHtQ== nop之后HzsW71C+Z3r+/Y1tWAsffg== 这里是iv
        UnidbgPointer pointer = UnidbgPointer.pointer(emulator, module.base + 0x29384); //这里要分为16个字符长度,每个字符跟bcda123fcd4d2019的相应位置异或。得到sm4加密之前的明文输入,1234得到的是SQWU=>?joh8h><=5
        Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
        String s = "nop";
        byte[] machineCode = keystone.assemble(s).getMachineCode();
        pointer.write(machineCode);
        pointer = UnidbgPointer.pointer(emulator, module.base + 0x29388);
        keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
        machineCode = keystone.assemble(s).getMachineCode();
        pointer.write(machineCode);
        pointer = UnidbgPointer.pointer(emulator, module.base + 0x2938C);
        keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
        machineCode = keystone.assemble(s).getMachineCode();
        pointer.write(machineCode);
//
//arg listm
        ArrayList<Object> params = new ArrayList<>(10);
//jnienv
        params.add(vm.getJNIEnv());
//jclazz
        params.add(0);
        //str1 参数//用4个字符刚好就是
        StringObject str1 = new StringObject(vm, "F1234");
        params.add(vm.addLocalObject(str1));
//int 参数2
        params.add(0);

        //str2 参数3
        StringObject str2 = new StringObject(vm, "1715591022250");
        params.add(vm.addLocalObject(str2));
        Number number = module.callFunction(emulator, 0x1DDE0, params.toArray());
        StringObject res = vm.getObject(number.intValue());
        return "";
    }

    public static void main(String[] args) throws FileNotFoundException {
        bydhy b = new bydhy();
        System.out.println(b.checkcode());
    }
}

这里F1234作为明文输入,patch iv之后的结果为1f3b16ef 50be677a fefd8d6d 580b1f7e

分别输入改x0就行,手动改,拿28轮举例,最终的结果。


其他轮次依旧这样,最后的结果如下。


phoenixSM4 之后为:

import phoenixSM4

with open('tracefile', 'wb') as t:
    t.write("""1f3b16ef50be677afefd8d6d580b1f7e
c0d87ff54e212f92fefd8d6d580b1f7e
a2cd206a4f222e95fefd8d6d580b1f7e
bc2d8a7c4e212f95fefd8d6d580b1f7e 
9e43147076bf48e5b2880a15580b1f7e
5e7c86247cbb2debb38b0b12580b1f7e 
16682a9fa8e47260b38b0b15580b1f7e 
f9c2ca9329900216a37f2c29ea249ee4 
b8025b071088252c133e42ebeb279fe3
d3e683f0531d532a276536b7ea249ee3 
397d454b8f19d16f2fd06790e9b61b33
87cc59fa3276e157824284314ddaa83f
65cc8506759bc518c1ad173c64882580
""".encode('utf8'))

phoenixSM4.crack_file('tracefile')
结果:
Round key 32 found:
21E6B235
Round key 31 found:
B2D12B44
Round key 30 found:
682E0F96
Round key 29 found:
EDF3A9FA
./sm4_keyschedule EDF3A9FA 682E0F96 B2D12B44 21E6B235 32   
Key: 39B8EC81 9A4A5585 40AFD76E 142A2B9E
K00: 9A095647 CCE066D5 27D246F9 A65A0942
K04: 83101FAD B7DE1D60 A35DF2E1 A62C2EDD
K08: 3C444D94 493CE50E B71B752B 09D66C42
K12: 42EAEDE4 42782EB5 3104CB06 8C7525F0
K16: 57739F57 2C376B48 FB588F56 6A317921
K20: 9D5C5CED D32E709F BBBABC1F FCF3E7F8
K24: 4F242670 E9A12B08 DCC90826 90BFEA02
K28: 73521288 8B2E1AC9 1763BA27 EF90DD2E
K32: EDF3A9FA 682E0F96 B2D12B44 21E6B235

修改端序
./sm4_keyschedule FAA9F3ED 960F2E68 442BD1B2 35B2E621 32                                  
Key: 01234567 89ABCDEF 01234567 89ABCDEF
K00: A292FFA1 DF01FEBF 665ED4F0 3BDBEF33
K04: F12186F9 41662B61 C15C6744 527431C6
K08: 84D52B8C 494B8F3E 467E18C9 E47F3D90
K12: 960C4962 47CEAA78 76D78969 0CF3CC88
K16: C78AC0F5 354D88D4 666F8E03 A3993068
K20: 92777521 0A6A9446 F7FC7268 73C658F6
K24: D2A9C5AC 7F58ED7B C6ED22D9 E8FB6FFF
K28: 35640B48 C81955CD 0B24484F 0D3FE944
K32: FAA9F3ED 960F2E68 442BD1B2 35B2E621

注意点

这里最后的结果我也没找到差异,[原创]白盒SM4的DFA方案-Android安全-看雪-安全社区|安全招聘|kanxue.com 这边文章提示了我,第32轮注入的话影响4字节,我选的位置是没有进行轮秘钥处理的,所以这位置不可取,后续也只有一个反序,所以忽略。大佬也说了有轮密钥端序的问题,最后处理下就行。

本文系作者 @ 原创发布在 我的编程学习之路。未经许可,禁止转载。

喜欢()
热门搜索
30 文章
0 评论
541 喜欢
Top