比亚迪hy SM4 白盒 DFA 攻击
案例:比亚迪hy
https://github.com/py-gmssl/py-gmssl 可以看源码
https://github.com/guojuntang/sm4_dfa dfa攻击
分析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轮举例,最终的结果。
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字节,我选的位置是没有进行轮秘钥处理的,所以这位置不可取,后续也只有一个反序,所以忽略。大佬也说了有轮密钥端序的问题,最后处理下就行。
本文系作者 @吾爱小白 原创发布在 我的编程学习之路。未经许可,禁止转载。