吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6061|回复: 101
上一主题 下一主题
收起左侧

[Android 原创] byd王朝白盒AES分析

  [复制链接]
跳转到指定楼层
楼主
xiayutianz 发表于 2025-11-11 17:02 回帖奖励

byd王朝白盒AES

1. 概述

版本:7.7.0

包名:com.byd.aeri.caranywhere

加固:梆梆加固企业版

  • 样本有加固,先把前面的东西说了,脱壳使用fart或者其他方式,版本不高随便脱;
  • 由于是bb加固,frida检测也是有的,但是版本比较低,检测强度也一般,读者需要复现的话请自行寻找魔改方案;
  • 观看这篇文章需要对AES加密流程有一定的了解,默认你有;

2. 抓包

  • 目标接口是登录,直接抓包发现网络超时;

image-20251111103402774

  • 对应的包也是经典的ssl pinning报错,证明是有抓包检测的,先试试模块JustTrustMe,发现依旧是不行,由于抓包的时候已经知道了是ssl的问题,猜测可能是混淆了;
  • 这里一般的方案是去找混淆的位置进行hook,这样会比较麻烦;这里有一个小技巧,军哥开发的算法助手有JustTrustMe功能,而且还是升级版,对混淆有很好的效果,如下图:

image-20251111103910243

  • 成功的抓到了包:

image-20251111104123812

  • 接下来的目标就是解决这两个被加密的数据;

3. 白盒AES分析

  • 前面说了脱壳的方案,直接搜索 "request" 定位,比较好找,位置如下:

image-20251111104812846

  • checkcode应该是加密,decheckcode应该就是解密的方法了;对应的so应该是libencrypt.so;
  • 先hook一下看看位置是不是正确;
function hook_java() {
    Java.perform(function () {
        let CheckCodeUtil = Java.use("com.bangcle.comapiprotect.CheckCodeUtil");
        CheckCodeUtil["checkcode"].overload('java.lang.String', 'int', 'java.lang.String').implementation = function (str, i, str2) {
            console.log(`CheckCodeUtil.checkcode is called: str=${str}, i=${i}, str2=${str2}`);
            let result = this["checkcode"](str, i, str2);
            console.log(`CheckCodeUtil.checkcode result=${result}`);
            return result;
        };
    })
}

hook_java()
  • 依旧以 f 的方式启动,是没办法attach的;

image-20251111105130567

  • 报错啦,经典的classloader问题,切换一下,脚本如下:
function hook_dyn_dex() {
    Java.perform(function () {
        //hook 动态加载的dex
        Java.enumerateClassLoaders({   //枚举classLoader
            onMatch: function (loader) {
                try {
                    if (loader.findClass("com.bangcle.comapiprotect.CheckCodeUtil")) {
                        console.log(loader);
                        Java.classFactory.loader = loader;      //切换classloader
                    }
                } catch (error) {
                }
            }, onComplete: function () {
            }
        });
    });
}

function hook_java() {
    Java.perform(function () {
        let CheckCodeUtil = Java.use("com.bangcle.comapiprotect.CheckCodeUtil");
        CheckCodeUtil["checkcode"].overload('java.lang.String', 'int', 'java.lang.String').implementation = function (str, i, str2) {
            console.log(`CheckCodeUtil.checkcode is called: str=${str}, i=${i}, str2=${str2}`);
            let result = this["checkcode"](str, i, str2);
            console.log(`CheckCodeUtil.checkcode result=${result}`);
            return result;
        };
    })
}

setTimeout(hook_dyn_dex, 1000)
setTimeout(hook_java, 2000)
  • 这里稍微延迟一下,不然也会报错,虽然不影响hook;

image-20251111105922252

  • 没有问题,位置没错,选定一组数据,后续以它为标准;
CheckCodeUtil.checkcode is called: str=F{"encryData":"949563ADA4143C41147A2034A9C0E8927F8574D8CBEE7C9E014DE4B25D77CB7DC360F64DA2C897655FFFCD928787B0E7585FBE259B2DB8ACA758CC689AFF470BC847187B90562F1EB65C8ED889EDBB26D6C9E2161A565A0FF79378A2868CEEF55BDC6604EDCF37C971FC389918A80412BEAB5969619A02F92CB8C8FA1EA87278AF6CD9ACD93D30F3BA46A30BCD76D74EB010A85AC8EFBE8301989DE9E65E92F152E96670000B089C7C795EA9BD5D0BBF8F789C40020BCC76809058F3F6D56FD98F21929907DF80E354BC06C9719145AFA7F83B13F59509E11A6D50DD23D99C1217C29C4818F0B48FFD487555FBF1665D928ED18DE4FC1FA4E0961C995B2C18F1C23C0357ADCE77C716A1C28C7D5CFA87EB92D862E261411B0CEDFA269A5D2BA9F9596B02567A2A2845CDB5A6D55B60F772C24B504545AD8F48A360FFEDC38F124924CB181D1F8BE9E3EE4CE04C81F2BA4C90C6974DC69DD0C6BC6BEA7FA305EF00002E8DD30A21BC96359B21659862F88A3070942C1658D3F309284684797B789FFF6CF6AB78CBA267CBE7A2251012B116FE4ABBA4CFF73F5C16C5A74F7091D46F2CFD31872D5A00CB3BA94AC1D23F1A68407AA287262060137009C7BB78C1EE7AF54AC5D0EF968024F7CDEF8CBC380734924F0B4CC01A583CC91FF50AC00DB9","identifier":"15233334444","identifierType":"0","imeiMD5":"5687F2D041BDEFD884DD468B3449004E","isAuto":"1","loginType":0,"reqTimestamp":"1762830389140","sign":"A3a36Aea92e699b378be542846c786415C60F9fa","appChannel":"1"}, i=1, str2=1762830426477
CheckCodeUtil.checkcode result=FG7zvCpTEprDTez3LK7OuzDuFk9vgIgKi4qvd2JkmB25PtXlpJ+R1e0ZVr5uQu5TY+XNWbOlisMR7LF7ZQ7CnZ6SB3/Qdzyikf78XpE1zZqenhiiLmrup1L1rjec7ixspOiceBOVhdGxBJUPPzcPPI9Hlj4ZTx2b+cRGa5vcvU3X1WnsAPsngm/moePo4oZraXVRfe59KRmhFo5eOF3lj9tQVmFgdXMyWz4GQhVALTxQ0PIzxF5wKgEL4Q1+A+UBRqcJpqBfjXS1+fofC4Zbe8SW7W63PiCkjrGYBumlwn5TuTf40ODA0woFZqi0UyhVT6kK9ofBexgyXIeJKOWfC+64MgZWrwZdbaVsQ+W/oxU1GWJ0EjjY0NrfsFiyONQ9Hvx6nKazQCAfuUHCUXeEBK2kfdiSFtA8W9dM5e7hotqnWR/08hcR7UpCX+ICk7zUSdZ2zrKB5MupwZNz8+BKpSYPfkkK+KmX4wuqTsq5mi47fIWbLtlWszDWHTWh0b4Ch/okBYcf5FDH4DWT74Hwhz3Z0GWjmDkKtprXMS8zcgUYrp1azbtSio951IoeaEXJ+umzNao2wFpsUEW/gLKUR6rqq0q+dEVrT7uzSjN3xsAJbz9naOm9wbJhDuee7a3ubrnxeHG/6yqBRTFUjONexKj+Yu4pmtINFiSZJLSv4uhmx2kdfxlJeYOyJTS0Vp6YH/686mjSYNqUufAfNmwD0k0LtZglJPTvy3XLU0HH9RA9A0I1f9wjOQQGdQDhJdcADhJHd3V1WPz0QXbu6CMqr5OGHv4KNLTvVJhbvAgjCpcjXxpYIvDSyhhsQc1IO/tkxP3AKqUyg8Tfsp8TIVKaLJ2Cma/dLgSVwshQzjJQlIB2wsvCTZeclNlW4O36UEGpfUonKlL4xt66PX8qPCZIJTWrmmPPOJ6iHS3lMi20NIpXwqz8tRlAfGJOym5eBKX9wxgTAMjPjT95+k5fwFCuWR6bYXeOGh+fOFKmBbBn8nnrFlu5wa+2pbr0dzTHSDeKBQ9fdDh8tr+Gmg/cKz7JRw5Kg0UlCBp4I24Z3BzWjXca4+7z7DKu1JLEKd05dbfkPAYBBEGxq1QRWrX/2o6kkfXr9hpebXZTUX1EpmO47d9RgNO53JYBNZjQOoCS1Z7l3a6mDJPd88tSOKFZl8NMzXm5i8zI9KleRFhZtdHIeolfYKhi8Tq56lscxBwMDalTKEINO13LJUjVYmEQFph1id7MSE+uaM2p/R2YtrqUJRmS960MdFZTDxNPechqbiXHofbKX2vimNSrQ6FTPjXM8ap9DhSMvbc0BcGy0zyA/aYblSIZnsmrp1CchWZq0Ih9nWdh4fZAJNyc3UDnf7QGuzIix3IoHAAvuB7THdc6UN9CKliuGUkcmSD3Uj03P92z1j0WXDNl2TVC+DtYSCfOynWLoZUqqJqtRQZHdjQ8BqnuV4tJIIqzDougZp0eUaFrUeIx4dJ/dHqVL3L0i2LeUQE/ihsOE2vn2hEP4tYScYijAZVevHdbOr4M+rAudusDA2dCg59Q1BAXV0bdXfNFq+rFyN1QRrQ8YJlFNsPXNXUviCWBZYypJ9UJC0Q+9v+n/UW28OVMMpznW2v5iXx72IAWmyja0VsD3IKDxZmFLbrEq3aafkTDfExJBT3Dg3dWPOt/5V7cRqj3BERzkFhRuxFqvkyzMKb4qS7IaXzb6Xj+tfnLyu+EmEcn6RLk378q1oIHA/6eqUfDSou0teaxdW4LzrqwwczYVlUjDjdoUAds9iGNHzsU2eDYuXYLHJvmXP70dwiHbgadUdyyxA2XdSNBaQk9zBEszwn+QxqvOpg4=
  • 再去看看so,这个样本不写主动调用,使用unidbg辅助分析;

image-20251111110935115

  • 一眼加固,去dump一下,依旧一把梭;

image-20251111111100921

  • 这里试探性的搜了一下,还真是静态注册,省事了;

image-20251111111212684

  • 反编译,5000+行代码···

image-20251111111456492

  • 直接看伪代码就别想了,通过其他方式去定位数据来源吧,先模拟执行;

  • 模拟执行没啥难度,补环境的代码这里也一并给出了,maps不补感觉也没什么关系,call的话是前面存的那组数据;

package com.Samples.byd;

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 java.io.File;
import java.util.ArrayList;
import java.util.List;

public class byd 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("file open pathName:" + pathname);
        int pid = emulator.getPid();
        if (pathname.equals("/proc/" + pid + "/maps")) {
            return FileResult.success(new SimpleFileIO(oflags, new File("src/test/java/com/Samples/byd/file/maps"), pathname));
        }
        return null;
    }

    public byd() {
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.byd.aeri.caranywhere").build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File("src/test/java/com/Samples/byd/file/比亚迪王朝v7.7.0.apk"));
        emulator.getSyscallHandler().addIOResolver(this);
        new AndroidModule(emulator, vm).register(memory);
        vm.setVerbose(true);
        vm.setJni(this);
        DalvikModule dm = vm.loadLibrary(new File("src/test/java/com/Samples/byd/file/libencrypt.so_0x7200c1d000_1937408_fix.so"), true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {
        byd demo = new byd();
        demo.hookMemcpy();
        ;
        String res = demo.call_checkcode();
        System.out.println("[+]主动调用result-->>" + res);
    }

    public String call_checkcode() {
        //arg list
        ArrayList<Object> list = new ArrayList<>(10);
        //jnienv
        list.add(vm.getJNIEnv());
        //jclazz
        list.add(0);
        //str1  参数1
        StringObject str1 = new StringObject(vm, "F{\"encryData\":\"949563ADA4143C41147A2034A9C0E8927F8574D8CBEE7C9E014DE4B25D77CB7DC360F64DA2C897655FFFCD928787B0E7585FBE259B2DB8ACA758CC689AFF470BC847187B90562F1EB65C8ED889EDBB26D6C9E2161A565A0FF79378A2868CEEF55BDC6604EDCF37C971FC389918A80412BEAB5969619A02F92CB8C8FA1EA87278AF6CD9ACD93D30F3BA46A30BCD76D74EB010A85AC8EFBE8301989DE9E65E92F152E96670000B089C7C795EA9BD5D0BBF8F789C40020BCC76809058F3F6D56FD98F21929907DF80E354BC06C9719145AFA7F83B13F59509E11A6D50DD23D99C1217C29C4818F0B48FFD487555FBF1665D928ED18DE4FC1FA4E0961C995B2C18F1C23C0357ADCE77C716A1C28C7D5CFA87EB92D862E261411B0CEDFA269A5D2BA9F9596B02567A2A2845CDB5A6D55B60F772C24B504545AD8F48A360FFEDC38F124924CB181D1F8BE9E3EE4CE04C81F2BA4C90C6974DC69DD0C6BC6BEA7FA305EF00002E8DD30A21BC96359B21659862F88A3070942C1658D3F309284684797B789FFF6CF6AB78CBA267CBE7A2251012B116FE4ABBA4CFF73F5C16C5A74F7091D46F2CFD31872D5A00CB3BA94AC1D23F1A68407AA287262060137009C7BB78C1EE7AF54AC5D0EF968024F7CDEF8CBC380734924F0B4CC01A583CC91FF50AC00DB9\",\"identifier\":\"15233334444\",\"identifierType\":\"0\",\"imeiMD5\":\"5687F2D041BDEFD884DD468B3449004E\",\"isAuto\":\"1\",\"loginType\":0,\"reqTimestamp\":\"1762830389140\",\"sign\":\"A3a36Aea92e699b378be542846c786415C60F9fa\",\"appChannel\":\"1\"}");
        list.add(vm.addLocalObject(str1));
        //int 参数2
        list.add(1);
        //str2 参数3
        StringObject str2 = new StringObject(vm, "1762830426477");
        list.add(vm.addLocalObject(str2));

        Number number = module.callFunction(emulator, 0x253ac, list.toArray());
        StringObject res = vm.getObject(number.intValue());
        return res.toString();
    }

    @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, "Pixel");
            }
            case "android/os/Build->MANUFACTURER:Ljava/lang/String;": {
                return new StringObject(vm, "Google");
            }
            case "android/os/Build$VERSION->SDK:Ljava/lang/String;": {
                return new StringObject(vm, "31");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    private void hookMemcpy() {
        emulator.attach().addBreakPoint(module.base + 0x1A0EC);
        emulator.attach().addBreakPoint(module.findSymbolByName("memcpy").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                RegisterContext context = emulator.getContext();
                int len = context.getIntArg(2);
                UnidbgPointer pointer1 = context.getPointerArg(0);
                UnidbgPointer pointer2 = context.getPointerArg(1);
                UnidbgPointer pointer3 = context.getLRPointer();
                Inspector.inspect(pointer2.getByteArray(0, len), "dest " + Long.toHexString(pointer1.peer) + " src " + Long.toHexString(pointer2.peer) + " lr " + Long.toHexString(pointer3.peer));
                return true;
            }
        });

    }
}
  • 环境没有什么难度,在执行之前我比较习惯把memcpy给hook上,这是一个良好的习惯,在这个样本尤其体现出来;
  • 另外,在执行之前需要说几个点,首先,结果的开头 F 是一个固定的,而且是额外拼接的;

image-20251111143850089

  • memcpy也是可以证明这一点的,接下来开始找数据源从哪里来的;去traceWrite 40568000位置,这个地址的来源是上图的src;
emulator.traceWrite(0x40568000, 0x40568000 + 2);
  • 去看看打印结果;

image-20251111144332993

  • 位置对了,这里去看pc处的偏移 0x1a150;

image-20251111144614087

  • 发现它在一个base64_encode函数,那就是最后做了一次base64编码,那我们手工解码回去;

image-20251111144729671

  • 记住解码的结果,在分析的时候可以截个小图留在置顶方便分析;这里去看看base64传进来的是不是我们手工解码的数据,如果一直就证明它是标准的;
emulator.attach().addBreakPoint(module.base + 0x1A0EC);

image-20251111145014104

  • 确实是标准的,所以现在的目标变成了找1b bc ef这一段怎么来的,继续traceWrite,地址就是 0x4055ac00;
emulator.traceWrite(0x4055ac00, 0x4055ac00 + 2);

image-20251111145826237

  • 数据并不匹配,但是可以去看看 0x98ec;

image-20251111145910650

  • 之所以要来看就是因为有符号提示你,本身猜测就是对称加密,这里就大概可以心里有点底了;
  • 那1b bc ef可能就是aes加密的结果;
  • WBACRAES_EncryptOneBlock这个函数有些问题,找一下上图函数的交叉引用,它也叫WBACRAES_EncryptOneBlock;

image-20251111150104275

  • 这里有关键性的提示,到这里我们先不着急,汇总一下已知的情况;
    • aes128、cbc、白盒;
  • 正常来说我们需要找key和iv,白盒的话相对来说就不是正常的方法去找;
  • 继续接着上面往下说,要确定走没走,hook一下就知道了;

image-20251111151806354

emulator.attach().addBreakPoint(module.base + 0x98B4);
  • 这里肯定是找明文嘛,x1寄存器就是明文;

image-20251111151902888

  • hook到了证明确实走了,那就继续往下跟,去看里面的CWAESCipher::WBACRAES_EncryptOneBlock,它的偏移是9894,它也是走了的,但是函数实现比较可怜;

image-20251111152007388

  • 这种情况伪代码是不方便看了,那就去看汇编;

image-20251111152032643

  • 代码就几条,就不翻译了,BLR就是跳转,那我们就去inline hook一下,看看x4的值是多少,就是对应的函数调用;
emulator.attach().addBreakPoint(module.base + 0x98A8); 

image-20251111152214439

  • 对应的地址是0x7f68,跳过去看看;

image-20251111152247139

  • 这就是它真正调用的位置了,读者可以hook验证,是没问题的;
  • 接下来分析这个函数就行,最终的目的是dfa攻击,先看PrepareAESMatrix函数;

image-20251111153822226

  • 这个函数应该是一个矩阵state化的函数,应该说是一个矩阵准备函数,我们来验证一下;
  • 这里的a3就是最后的矩阵,hook的时候去看x2寄存器;
emulator.attach().addBreakPoint(module.base + 0x70E4);
  • 这里需要注意,x2直接看是得不出什么结论的,函数执行结束的时候再看,按blr下断点到lr寄存器,然后按c执行完成之后通过x2的值去读内容;这里切记是读地址而不是mx2;

image-20251111154149792

  • 直接看可能看着有点模糊,我稍微修剪一下;

image-20251111154405779

  • 这样就直观多了,0xbfffeb10 就是state的地址;这个地址后续会和key进行初始轮密钥加,但是在开始之前它应该要喝iv进行异或,所以这里的值应该是异或之后的结果才对;并且我们知道,0和其他数据异或,依旧是本身,这是一个特性,我们暂且猜测是这样;
  • dfa需要这样几个因素:找轮数、找state、找时机;
  • 轮数和时机可以看作一体,目标是最后两次列混淆之间进行就行,看下图就明白了;

image-20251111155109646

  • 也就是说:第8次的轮密钥加和第9次的字节代换和第9次的循环左移这三个时机均可;
  • 再就是找state,这个前面刚刚说过;再看整体的轮次;

image-20251111155313174

image-20251111155338168

  • 接下来就可以进行dfa了,找到一个合适的时机;这个读者可以随意选择,只要是在第一张图的9轮里里面就可以,先看看循环次数;这里有个点需要注意,明文记得改一下,否则分组次数太多了,我这里改的是hello;
   public void call_dfa(){
        Debugger debugger = emulator.attach();
        debugger.addBreakPoint(module.base+0x7ffc,new BreakPointCallback() {
            UnidbgPointer pointer;
            int num = 1;                    //m0xbfffeb10
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                pointer = UnidbgPointer.pointer(emulator, 0xbfffeb10L); // state
                System.out.println("num->"+num);

                num+=1;
                return true;
            }

        });

    }
  • 没问题,次数是合理的;

image-20251111160021701

  • 这里需要注意,万事俱备,但并没有所有事情都准备好,比如我们要看的是aes的结果,所以还需要额外写上一个看aes结果的hook,这个无所谓哪个方法,只要能hook到aes的输出就可以,也可以参照我的代码写;
  • 先看一次注入的结果,这里先不给dfa的代码:
正常:E6F6549E 5DCCB842 9ED21690 32665B6B
dfa:E6F6419E 5D6DB842 14D21690 32665BAF
  • 可以发现,异常出现在四个字节,这就是一个良好的故障密文,这个具体可以去看dfa的原理;接下来就是大量获取故障密文;
public static void main(String[] args) {
    byd demo = new byd();
    //        demo.hookMemcpy();
    for (int i = 0; i < 30; i++) {
        demo.call_dfa();
        demo.HookByConsoleDebugger();
        String res = demo.call_checkcode();
    }
    //        System.out.println("[+]主动调用result-->>" + res);
}

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

public void call_dfa() {
    Debugger debugger = emulator.attach();
    debugger.addBreakPoint(module.base + 0x7ffc, new BreakPointCallback() {
        UnidbgPointer pointer;
        int num = 1;                    //m0xbfffeb10

        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            pointer = UnidbgPointer.pointer(emulator, 0xbfffeb10L); // state
            //                System.out.println("num->" + num);
            if (num % 9 == 0) {
                Random random = new Random();
                int randomLine = random.nextInt(4); // 生成 0 到 3 之间的随机数
                int randnum = randint(0, 255);
                pointer.setByte(randint(0, 15), (byte) randnum); //随机注入
            }
            num += 1;
            return true;
        }
    });
}

public void HookByConsoleDebugger() {
    Debugger debugger = emulator.attach();
    debugger.addBreakPoint(module.base + 0x98b4, new BreakPointCallback() {
        int num = 0;

        @Override
        public boolean onHit(Emulator<?> emulator, long address) {
            RegisterContext context = emulator.getContext();
            num += 1;
            long x1 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X1).longValue();
            emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                @Override
                public boolean onHit(Emulator<?> emulator, long address) {
                    if (num == 1) {
                        Backend backend = emulator.getBackend();
                        byte[] bytes = backend.mem_read(x1, 0x10);
                        StringBuilder hexString = new StringBuilder();
                        for (byte b : bytes) {
                            hexString.append(String.format("%02X", b & 0xFF));
                        }
                        System.out.println(hexString);
                    }
                    return true;
                }
            });
            return true;
        }
    });
}
}
  • 打印密文之前把输出都取消掉,方便后续复制;

image-20251111163140229

  • 我这里是跑了30次,拿去dfa,这里要用到phoenixAES这个库,pip即可;
import phoenixAES

with open('data','wb') as f:
    f.write("""
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6D8549E37CCB8429ED216893266086B
11F6549E5DCCB8479ED2759032245B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6FE9E5D89B84222D2169032665B37
E69F549EDACCB8429ED216B832662A6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E68B549EE2CCB8429ED2168532664D6B
E609549E5FCCB8429ED216843266386B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
94F6549E5DCCB8D09ED20F9032AA5B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6545B5DCCC9429EFA169022665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6B2549E8BCCB8429ED2161A3266F96B
E6F654435DCCC6429E011690CA665B6B
1CF6549E5DCCB8829ED2089032545B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6549E5DCCB8429ED2169032665B6B
E6F6829E5D62B842A2D2169032665B75
E6F6549E5DCCB8429ED2169032665B6B""".encode())

phoenixAES.crack_file('data', [], True, False, 3)
  • 结果如下:

image-20251111163405844

  • 所以k10就是:AA648D03D06D1AC2CA9DEB861AC1388D;如果这里没推导出来就说明注入位置太单一了,换位置注入就好;
  • 这还只是k10,最初的key应该是k0,这个也有工具可以实现;

项目地址:https://github.com/SideChannelMarvels/Stark

  • 里面有一个aes_keyschedule.c文件,将其拷贝到本地并编译为可执行文件就可以使用命令行:
gcc aes_keyschedule.c -o WhiteBoxAes
  • 使用方式跟上秘钥和轮数就好;

image-20251111163808907

  • 初始的key就是:A87F1002B7DBC9FF882DC51F8A7DCFAD;结合之前分析的iv应该是全0,去测试一下;

image-20251111164113728

  • 响应也是正常解密的;

image-20251111164147941

4. 总结

  • 白盒AES的分析大概就是这么多,几百个字实际上分析起来没那么简单,还是花费了不少时间,你能独立把秘钥找出来白盒aes就基本不是难事了;
  • 另外,参数里有大量未知数据,还有个不知道哪来的checkcode,看着像md5,感兴趣的读者可自行去分析;我不想再看了;
  • 整体的逻辑可能不是特别通顺,毕竟分析的时候踩坑了到了写文章却会直奔正确答案,多多担待;
  • by:2025-11-11;

免费评分

参与人数 27吾爱币 +21 热心值 +24 收起 理由
junjia215 + 1 + 1 用心讨论,共获提升!
Golive180 + 1 + 1 看着就很专业
Huibq120 + 1 + 1 牛!
5210LYX + 1 + 1 我很赞同!
swz7852151 + 1 + 1 热心回复!
Hik222 + 1 + 1 我很赞同!
fengbolee + 1 + 1 谢谢@Thanks!
ioyr5995 + 1 + 1 我很赞同!
lxl12345 + 1 我很赞同!
凌云9 + 1 + 1 我很赞同!
timeni + 1 + 1 用心讨论,共获提升!
Quinn01 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
hsxt + 1 厉害
snowfox + 1 谢谢@Thanks!
FLynn-G + 1 我很赞同!
whit15 + 1 + 1 用心讨论,共获提升!
USG + 1 + 1 谢谢@Thanks!
lwGoodChinese + 1 + 1 用心讨论,共获提升!
cxs808 + 1 我写过小程序的破解,可供参考
白猫i- + 1 谢谢@Thanks!
buluo533 + 1 + 1 用心讨论,共获提升!
ocic + 1 + 1 楼主请问下&amp;quot;encryData&amp;quot;:&amp;quot;94956解密这个的key在哪儿我没找到,密钥是sh ...
zhangbo119 + 1 鼓励转贴优秀软件安全工具和文档!
mkbkl + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
dajusrc + 1 谢谢@Thanks!
zg2600 + 1 + 1 谢谢@Thanks!
helian147 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
cqrg 发表于 2025-11-12 11:43
终于看到有人对BYD下手了,玩HA的byd车主都想从app里获取数据,感谢
头像被屏蔽
推荐
ocic 发表于 2025-11-13 10:07
xiayutianz 发表于 2025-11-12 22:42
因为没需求 所以可能不太会看这个了

sign我算出来了,问题又来了,迪子太狗了,不知道sign用了什么叼毛规则,算出来的sign(sha1)40位 会出现部分位置的0被删除,

5Bd1 0f6ED53 E71F5b86D873C85F5abD4ba8Ca3 第6\14位的0被删除
5bd100f6ed530e71f5b86d873c85f5abd4ba8ca3 原始计算完整sha1

除了某些位置存在0被删除,还有一个问题就是某些位置的字母会大写 wcl头晕 找不出规则,楼主可以辛苦下帮我找找他这个sign某些位置字母大小写数字0删除的规则嘛~
沙发
YIUA 发表于 2025-11-11 18:41
3#
 楼主| xiayutianz 发表于 2025-11-11 22:11 |楼主
YIUA 发表于 2025-11-11 18:41
大佬,classloader问题是什么意思啊

目标类不是当前类加载器加载的就会报这种错
4#
nmdbld 发表于 2025-11-12 08:54
这也太厉害了
5#
cheneysnake 发表于 2025-11-12 09:08
请问所谓白盒是不是就是将密钥混合在计算过程中,加密时不再提供密钥,从而避免密钥泄露。
6#
hjw01 发表于 2025-11-12 09:17
学习了, 好文章
7#
caiaiii 发表于 2025-11-12 09:24
学习了, 好文章
8#
ydydq 发表于 2025-11-12 09:47
学习中,感谢了
9#
bawanglong01 发表于 2025-11-12 09:56
这个之前看到过类似的
10#
heayyy 发表于 2025-11-12 10:07

学习中,感谢了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-2-1 15:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表