吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3564|回复: 41
上一主题 下一主题
收起左侧

[Android 原创] 某车联网五零#光DFA攻击白盒AES

  [复制链接]
跳转到指定楼层
楼主
chenchenchen777 发表于 2024-12-26 13:52 回帖奖励
本帖最后由 chenchenchen777 于 2024-12-26 19:09 编辑

DFA攻击白盒AES

关于上一篇的数字加固的分析,图片没有处理好,之后会更新加固(中)
通过网盘分享的文件:llb.apk    链接: https://pan.baidu.com/s/1YPf2yyC-nFvxRKsweiNVGg?pwd=vbvm 提取码: vbvm
--来自百度网盘超级会员v4的分享
包名:com.cloudy.linglingbang

加固:梆梆

这里可以直接用FART来脱壳

frida检测:

应该是对于frida-server的特征检测,这里可以换上葫芦侠的frida就绕过了

这个APP在登录的过程中是没有抓包检测的,所以是可以直接去得到抓包结果的

user:12345678900   password:password777 

请求包

POST /llb/oauth/llb/ucenter/login HTTP/1.1
channel: yingyongbao
platformNo: Android
appVersionCode: 1481
version: V8.0.14
imei: a-4a674abf3d88252a
imsi: unknown
deviceModel: Pixel XL
deviceBrand: google
deviceType: Android
accessChannel: 1
oauthConsumerKey: 2019041810222516127
timestamp: 1734844726087
nonce: ypjWhicBQp
signature: b9114db2915d2611c075e8dcc85d1108
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 412
Host: api.00bang.cn
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0

sd=MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+ejw5L+QDI2qUfM/Xmay1TCEa7El9Gfq/uyfgxkHAuM7D4bz5YeAYxQnLbXjdyJst6bUOM1V7YePwSUmi0qQoDSYmTLK1n9d0RHzhqvK/qOrpBDxSho4c+di9p8yRar5pnQobZ5ErVnR5uUGWgh7Ap44oeKpLudkD9gK+O6E8gtD1R6/besf8zXt+lxE26QOfQIVOS/DBVobGJy/ReKJOQE6HC5WLQiwRqXY13bTdDoNJ3HYmatUVnQNANbIAS1tinA==

响应包

HTTP/1.1 200 OK
Date: Sun, 22 Dec 2024 05:18:47 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 458
Connection: keep-alive
Set-Cookie: acw_tc=ac11000117348447275828166efe3b6a04af5c6ec8ae3fd340bf080fd9a293;path=/;HttpOnly;Max-Age=1800
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Strict-Transport-Security: max-age=15724800; includeSubDomains

{"sd":"Mat4pSIuqCrzFWQE7ITVRYMZD4siD2ApWWETEZxDdSgG1F9pvAraW67KQ5iPdwYS3y6S1ff9X7DZ4p+AP8z9cxojG7xK/g+bIUWDPtAkmWbURumT2FRMiNCK8sdvQDlp7uuxHlKzUR7nmlktwx2JLfEihyGNimqumr7+lOqoAhJfgzk4M7r1p/r327SM/sZbRj9QsNooK4v9bgUAVcCoblD1dLtYIXA700ZodZ4cmhjmZWArG/cWhd2dSht0JZgN7vAHm8nIFQg5svnIx18sNu0QAk3ChsmWSx2Qk6RvDs25S0dLrTV9BF3jNuv1ucAz5OaOH82/ZeJ/t1Qaw0GE929+o8BpYL0olO/SFhQ8XH6jA8W/Y2cpd07tkiYAAYfB5lxkRUhWRCKMz3JJjcU43Vgew7Vy0Qc6mHpzbPjecOvv0ltkorGA0/TcEfMcKjjsb"}

这么长的请求和响应,肯定不是摘要啥的,多半就是AES,DES之类的了

我在这里通过通杀算法去查看了一些相关的请求包的数据,但是sd的信息是没有找到的,唯独只有signature这个签名有信息

复现 signature

signature: b9114db2915d2611c075e8dcc85d1108


这里可以看到的是,signature是直接的MD5的结果,然后根据堆栈去看看这个Signature是怎么生成的

可以看到的是这里的MD5之前的数据,其实就是固定值+时间戳+随机数+一个addHeader的结果

MD5 update data Utf8: 20190418102225161271734844726087ypjWhicBQpc5ad2a4290faa3df39683865c2e10310a14f0be589630ff5b16d35de3b0b7190

经过多次的抓包发现,其实最后的addHeader也是一个固定值,所以这里的signature是很好得到的

复现 sd值

由于我们的请求包里有sd变量,这里我们去搜索"sd"值,定位到了CheckCodeUtils类

查看哪里有对应的值的调用

看到这里append了"sd"以及encrypt,那么我们去HOOK一下这个值,来看看这个值的传入是和返回值是什么?

function HOOK_encryptfunction(){
    Java.perform(function (){
    let CheckCodeUtils = Java.use("com.cloudy.linglingbang.model.request.retrofit2.CheckCodeUtils");
CheckCodeUtils["encrypt"].implementation = function (str, i) {
    console.log(`CheckCodeUtils.encrypt is called: str=${str}, i=${i}`);
    let result = this["encrypt"](str, i);
    console.log(`CheckCodeUtils.encrypt result=${result}`);
    return result;
};
})
}
CheckCodeUtils.encrypt is called: str=mobile=12345678900&password=password777&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=F5bZmBhmPJ&response_type=token, i=2

CheckCodeUtils.encrypt result=MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+eu5g+46JW+icyQKS5lxXghApbG7B7VuAJOZK3zXGkh0cOSZ7cn8cVi1BL0xKTerAHZcQ7KB8sr7dGtDrdCnqyzeXFqCkNVP3KXD9DwULBbokyA/BTDauO8kXjkyso6w6sKt8HkaBTk3fUOvGWjv/ZqvbBaB/YFabiBjbG7PsF7zV/oLlgEVd/2QJYPKAjmpuBtcvjUZsygcNVB/HEKLytAS6FQ3NyaccAHI6WEPCLiblgu/HZaVgjcCjEr7TdziS3Q==

发现这里传入的就是我们的username和password,以及一些相关的参数

再往里走,发现是native函数

于是我们再去HOOK一下这个native函数看看

!

function Hook_checkcode(){
    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(`start [Method] CheckCodeUtil.checkcode is called: str=${str}, i=${i}, str2=${str2}`);
    let result = this["checkcode"](str, i, str2);
    console.log(`end   [Method] CheckCodeUtil.checkcode result=${result}`);
    return result;
};
}
CheckCodeUtils.encrypt is called: str=mobile=12345678900&password=password777&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=F5bZmBhmPJ&response_type=token, i=2

start [Method] CheckCodeUtil.checkcode is called: str=mobile=12345678900&password=password777&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=F5bZmBhmPJ&response_type=token, i=2, str2=1734847397441

end   [Method] CheckCodeUtil.checkcode result=MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+eu5g+46JW+icyQKS5lxXghApbG7B7VuAJOZK3zXGkh0cOSZ7cn8cVi1BL0xKTerAHZcQ7KB8sr7dGtDrdCnqyzeXFqCkNVP3KXD9DwULBbokyA/BTDauO8kXjkyso6w6sKt8HkaBTk3fUOvGWjv/ZqvbBaB/YFabiBjbG7PsF7zV/oLlgEVd/2QJYPKAjmpuBtcvjUZsygcNVB/HEKLytAS6FQ3NyaccAHI6WEPCLiblgu/HZaVgjcCjEr7TdziS3Q==

CheckCodeUtils.encrypt result=MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+eu5g+46JW+icyQKS5lxXghApbG7B7VuAJOZK3zXGkh0cOSZ7cn8cVi1BL0xKTerAHZcQ7KB8sr7dGtDrdCnqyzeXFqCkNVP3KXD9DwULBbokyA/BTDauO8kXjkyso6w6sKt8HkaBTk3fUOvGWjv/ZqvbBaB/YFabiBjbG7PsF7zV/oLlgEVd/2QJYPKAjmpuBtcvjUZsygcNVB/HEKLytAS6FQ3NyaccAHI6WEPCLiblgu/HZaVgjcCjEr7TdziS3Q==

发现这里的CheckCodeUtil.checkcode的结果就是请求包的结果

POST /llb/oauth/llb/ucenter/login HTTP/1.1
channel: yingyongbao
platformNo: Android
appVersionCode: 1481
version: V8.0.14
imei: a-4a674abf3d88252a
imsi: unknown
deviceModel: Pixel XL
deviceBrand: google
deviceType: Android
accessChannel: 1
oauthConsumerKey: 2019041810222516127
timestamp: 1734847438319
nonce: zT75O2UArt
signature: 808ab5ac781f83b6439b1e0f3c218d51
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 412
Host: api.00bang.cn
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/4.9.0

sd=MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+eu5g+46JW+icyQKS5lxXghApbG7B7VuAJOZK3zXGkh0cOSZ7cn8cVi1BL0xKTerAHZcQ7KB8sr7dGtDrdCnqyzeXFqCkNVP3KXD9DwULBbokyA/BTDauO8kXjkyso6w6sKt8HkaBTk3fUOvGWjv/ZqvbBaB/YFabiBjbG7PsF7zV/oLlgEVd/2QJYPKAjmpuBtcvjUZsygcNVB/HEKLytAS6FQ3NyaccAHI6WEPCLiblgu/HZaVgjcCjEr7TdziS3Q==

那么我们就去看看这个SO函数

全是JNI函数,我们尝试用unidbg来调用这个so来加载看看,结果所以我们要去补环境

uidbg

主动调用会报错,这样就要去补环境来启动Java_com_bangcle_comapiprotect_CheckCodeUtil_checkcode

同时这里是32位程序,记得HOOK的时候地址+1

补字段
@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 XL");
        }
        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, "29");
        }
    }
    return super.getStaticObjectField(vm, dvmClass, signature);
}
补callObjectMethod
    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":{
//                System.out.println("22222");
                return vm.resolveClass("android/app/ContextImpl").newObject(null);
            }
            case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;": {
                return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
            }
            case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{
                String arg = varArg.getObjectArg(0).getValue().toString();
//                System.out.println("getSystemService arg:"+arg);
                return vm.resolveClass("android.net.wifi").newObject(signature);
            }
            case "android/net/wifi->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, "02:00:00:00:00:00");
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }
补callStaticObjectMethod

这里的"ro.serialno":序列号,随便填也行

adb shell getprop ro.serialno
    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature){
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":{
                return vm.resolveClass("android/app/ActivityThread").newObject(null);
            }
            case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{
                String arg = varArg.getObjectArg(0).getValue().toString();
                System.out.println("SystemProperties get arg:"+arg);
                if(arg.equals("ro.serialno")){
                    return new StringObject(vm, "HT7650200010");
                }
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }
总体代码:(这里是如烟大佬的代码,我的之前因为那个字段补环境的地方已经改了很多了)
package com.linglingbang;

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

public class demo2 extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final Memory memory;

    demo2(){
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.cloudy.linglingbang").build();
        // 获取模拟器的内存操作接口
        memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("E:\\android\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\linglingbang\\llb.apk"));
        // 设置JNI
        vm.setJni(this);
        // 打印日志
        vm.setVerbose(true);
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("E:\\android\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\linglingbang\\libencrypt.so"), true);
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        // 调用JNI OnLoad
        dm.callJNI_OnLoad(emulator);
    };
    public String callByAddress(){
        // args list
        List<Object> list = new ArrayList<>(5);
        // jnienv
        list.add(vm.getJNIEnv());
        // jclazz
        list.add(0);
        // str1
        list.add(vm.addLocalObject(new StringObject(vm, "mobile=12345678900&password=password777&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=F5bZmBhmPJ&response_type=token")));
        // int
        list.add(2);
        // str2
        list.add(vm.addLocalObject(new StringObject(vm, "1734847397441")));
        Number number = module.callFunction(emulator, 0x13A19, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        System.out.println(" CheckCodeUtils.encrypt result encrypt ="+result);
        return result;
    };
    public static void main(String[] args) {
        demo2 llb = new demo2();
        llb.callByAddress();
    }
    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature){
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":{
                return vm.resolveClass("android/app/ActivityThread").newObject(null);
            }
            case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{
                String arg = varArg.getObjectArg(0).getValue().toString();
                System.out.println("SystemProperties get arg:"+arg);
                if(arg.equals("ro.serialno")){
                    return new StringObject(vm, "9B131FFBA001Y5");
                }
            }
        }
        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;":{
//                System.out.println("22222");
                return vm.resolveClass("android/app/ContextImpl").newObject(null);
            }
            case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;": {
                return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
            }
            case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{
                String arg = varArg.getObjectArg(0).getValue().toString();
//                System.out.println("getSystemService arg:"+arg);
                return vm.resolveClass("android.net.wifi").newObject(signature);
            }
            case "android/net/wifi->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, "02:00:00:00:00:00");
            }
        }
        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 XL");
            }
            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, "29");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }
}

这里是算法通杀HOOK得到的结果


再来看看我们unidbg得到的结果

可以看到的是,结果是一样的,那么也就是说这里native函数完成之后的返回值就是这个了

如画这里同时去调用了Java_com_bangcle_comapiprotect_CheckCodeUtil_decheckcode,所以我也去看了看调用

    public void call_decrypto(){
        ArrayList<Object> list = new ArrayList<>(5);
        String str = "MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+eu5g+46JW+icyQKS5lxXghApbG7B7VuAJOZK3zXGkh0cOSZ7cn8cVi1BL0xKTerAHYLxENn4IHSAkILI6kZfeRuEjaSrpUA6KgEkR96849oPfbphCeHESmH12gIqnuJoTTXxjwDfKMy0kplSVK/GJwid6z6fkxUwUGP4tw43TkyqE+XiWflyamfvLKNOlycj9gKvOjmH5swX89TeaNCfk9JG93uHZ7zT2XBx8bFmRy5zazj2hmSD5+TCYIA/eh7iMFzdguMrfygLLpt7MwDG6xY=";
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new StringObject(vm,str)));
        Number number = module.callFunction(emulator, 0x0165E1, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        System.out.println(" CheckCodeUtils.call_decrypto result encrypt ="+result);
    }


按道理来说我们加密传入的是

mobile=12345678900&password=password777&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=F5bZmBhmPJ&response_type=token&ostype=ios&imei=9B131FFBA001Y5&mac=02:00:00:00:00:00&model=Pixel XL&sdk=29&serviceTime=1734847397441&mod=Google&checkcode=0c30ff815f9539478b978a39c9c95d91

那我们解密得到的就应该也是这个才对,但是这里的值一看就大概率是MD5之后的值,怎么解密之后得到的是MD5呢?


我们可以看到这里也会返回值,那么大概率就是返回v5的值了,v5又是加入了sub_138AC函数的


同时在这函数里面又调用了其他函数,在这里我们找到了类似于MD5的64轮加密的算法


所以这里我们patch一些代码,让程序不走MD5的分支,看看能够得到什么



结果就直接解密了

复现加密算法

从现在开始我们就来复现加密算法了,直接去Java_com_bangcle_comapiprotect_CheckCodeUtil_checkcode看加密算法了

这里的代码一共有大概2900行的代码,其中有很多都是MD5加密的过程(大概从1300行-2700行之间全是MD5算法)


在2787行的位置出现了AES的加密函数,但是里面是bss段的信息


我们通过交叉引用来看看能不能得到对应位置的代码



其实能够看都这里的是有对应位置的加密函数的,大概率是aes_encrypt1


这里我们也可以通过汇编去看看代码,发现这么其实是跳转到的R3的对应寄存器的位置的值,那么我们就可以去HOOK对应位置得到对应的R3的值



确定了就是在aes_encrypt1的位置,然后我们就发现了WBACRAES128_EncryptCBC的字眼,大概率就是白盒CBC模式的AES128了


往里走可以看到对应的填充的函数以及,加密一个块的函数

来到这里的CWAESCipher::WBACRAES_EncryptOneBlock


发现是一个看不了的函数,有点类似于SMC,我们也去看汇编

]
发现跳转是R4的位置,我们添加断点,查看对应寄存器R4的值

    public void HOOK_unline()
    {
        attach.addBreakPoint(module.base+0x163FE);//得到aes_encrypt1的地址005a35-1
        attach.addBreakPoint(module.base+0x0005836);//得到WBACRAES_EncryptOneBlock的地址04dcd-1
    }

在CWAESCipher_Auth::WBACRAES_EncryptOneBlock函数中有明显的表面AES轮数的位置


在九轮之后就不会进行列混淆了,而我们要得到密钥就是通过DFA差分攻击来实现得到密钥,https://bbs.kanxue.com/thread-280335.htm这里有一篇对于AES的DFA差分攻击很详细的帖子

DFA

在AES的state的正常执行流中,替换错误的一个字节的数据,导致处理错误。其中

  • 如果故障早于倒数第二个列混淆,那么会影响结果中的十六个字节

  • 如果故障发生在倒数两个列混淆之间,那么会影响结果中的四个字节

  • 如果故障晚于最后一个列混淆,那么会影响结果中的一个字节


    这里说的倒数两个列混淆也就是在   第八个和第九个循环之间的事情。

其中主要构成能够进行差分攻击DFA的主要原因是在固定了输出差分,也就是我们原本的加密结果和故障加密结果之间的差值(异或值),而导致我们可以去约束输入差分的范围


同时由于输出差分的固定,而且Y0和Z又是0-256之间的值,通过这样的算式我们同样实现了约束Y0,进而约束了K10,0的范围


在state的错误位置被修改的时候,也就是导致结果不同位置被故障之后,但是K10,0也就是第十个扩展密钥的值却是不变的,通过多次对于State故障位置的改变,一直去约束K10,0的范围,直到可以实现解密K10,0的值,同理便实现了K10的解密,进而得到密钥,这就是DFA的原理。通过state的故障位置的改变,增大约束范围,实现值的确定,进而得到真正的密钥结果。

这里为了使得伪造输入和查看填充方式,就跟着如画一样,把输入的传值修改了,我们选择的位置是在int __fastcall aes_encrypt1的位置

attach.addBreakPoint(module.base + 0x5A34, new BreakPointCallback() {       //修改输入为hello
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                String fackInput = "hello";
//                String fackInput = "helloworldDDDDDDD";
                MemoryBlock fackInputBlock = emulator.getMemory().malloc(fackInput.length(), true);
                fackInputBlock.getPointer().write(fackInput.getBytes(StandardCharsets.UTF_8));
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0,fackInputBlock.getPointer().peer);
                return true;
            }
        });

先看一下在不进行DFA之前的输入为hello的加密结果

然后我们开始进行DFA的差分攻击,随机的在state的16个字节的位置去随机替换一个值,注意这里的时机是在第八轮的列混淆之后,第九轮之前

attach.addBreakPoint(module.base+0x004E1A, new BreakPointCallback() {  //star to encrypto 故障
    int round = 0;
    final UnidbgPointer statePointer = memory.pointer(0xE4FFF458L);
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        round += 1;
        System.out.println("round:"+round);
        if (round % 9 == 0){
            statePointer.setByte(randInt(0, 15), (byte) randInt(0, 0xff));
        }
        return true;//返回true 就不会在控制台断住
    }
});
public static int randInt(int min, int max) {
        Random rand = new Random();
        return rand.nextInt((max - min) + 1) + min;  // min 到 max 之间的随机数
    }

这里的选择的state的替换的地址是要自己去找的

        attach.addBreakPoint(module.base+0x5888, new BreakPointCallback() {
            RegisterContext context = emulator.getContext();
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                Inspector.inspect("CWAESCipher::WBACRAES128_EncryptCBC \n0x5888 args[1] painText addrs : ", (int) context.getPointerArg(1).peer);
                Inspector.inspect("0x4DCC args[2] encrypto date  addrs : ", (int) context.getPointerArg(2).peer);

                emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
                    //onleave
                    @Override
                    public boolean onHit(Emulator<?> emulator, long address) {
                        Inspector.inspect("加密函数结束 Onleave  ",0x000000 );
                        return false;
                    }
                });
                return false;
            }
        });

是在CWAESCipher::WBACRAES128_EncryptCBC的三个参数的指针地址的位置。这样就可以在找到对应位置的地方进行差分攻击了



就这样的修改state的一个字节,然后约束范围,得到密钥

import phoenixAES
with open('tracefile', 'wb') as t:  # 第一行是正确密文 后面是故障密文
    t.write("""57b0d60b1873ad7de3aa2f5c1e4b3ff6
57b0630b18d8ad7de8aa2f5c1e4b3f73
57b0d65b1873e07de3752f5c804b3ff6
578bd60b2d73ad7de3aa2fa51e4b47f6
adb0d60b1873ad50e3aa225c1e983ff6
57e4d60b0173ad7de3aa2f061e4b17f6
17b0d60b1873ad02e3aa235c1efb3ff6
57b0460b185aad7d76aa2f5c1e4b3f16
5704d60bfd73ad7de3aa2fc21e4b4ef6
57b0870b186fad7d3baa2f5c1e4b3fd7
c3b0d60b1873add4e3aa745c1e103ff6
57b0d6531873af7de3302f5c964b3ff6
     """.encode('utf8'))
phoenixAES.crack_file('tracefile', [], True, False, 3)

这里的数据我重新进行了修改,把自己找到数据填上去了,结果也是一样的

8A6E30D74045AE83634D6ECDE1516CA1

通过K10得到K0  https://github.com/SideChannelMarvels/Stark 执行获得exe


这样就可以通过k10得到k0了,也就是我们的密钥了(原理在之前的AES的文章里面也有提到)   

F6F472F595B511EA9237685B35A8F866

细节推理:

首先是算法是什么模式,白盒的AES,CBC模式的填充方式大概率是pkcs7。那么我们先尝试去实现一下我们请求数据的加密过程看看,但是这里的CBC模式是不行的,因为我们还没有IV。但是不过是我们假设输入的hello和原始数据都不能被解密,那我们应该从哪里入手,这里站着了巨人的肩膀上才能够看到使用ECB模式也得到输入


但是为什么能够得到hello的字眼的结果,按照正常来说,假如是CBC模式,明文是要先进行IV异或之后再进行AES加密,之后的第一个块去异或下一个明文,也就是说按照道理,我们得到的也应该是明文和IV的异或值,正常来看,我们只能当成ECB模式来看了,那么我们就要去看看填充方式是什么了,因为EBC模式有No padding和PKCS7。

在padding之后设置断点就好了,不过先可以去得到a2的内存地址

attach.addBreakPoint(module.base+0x058A0);
attach.addBreakPoint(module.base+0x4DCC, new BreakPointCallback() {
    RegisterContext context = emulator.getContext();

    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        Inspector.inspect("实际加密函数CWAESCipher_Auth::WBACRAES_EncryptOneBlock \n0x4DCC args[1] painText addrs : ", (int) context.getPointerArg(1).peer);
        Inspector.inspect("0x4DCC args[2] encrypto date  addrs : ", (int) context.getPointerArg(2).peer);

        emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
            //onleave
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                Inspector.inspect("加密函数结束 Onleave  ",0x000000 );
                return false;
            }
        });
        return true;
    }
});


这里直接是填充的00 00 00,那么可以认为是No padding了

那我们就去加密

MLWVhswVAEjWdmSR3ypZ1P1WDq5OGNPucy60sGAPwAGoDF3K0RyeumBVEeGpOtHv7865YfI9iDCfXcUEvN96RBv9FlAqULcndldQZYML0lmq1TORKsx50wzN21QfhMzRmI8FY5Jo7XOhpEXd7Yp6+eu5g+46JW+icyQKS5lxXghApbG7B7VuAJOZK3zXGkh0cOSZ7cn8cVi1BL0xKTerAHYLxENn4IHSAkILI6kZfeRuEjaSrpUA6KgEkR96849oPfbphCeHESmH12gIqnuJoTTXxjwDfKMy0kplSVK/GJwid6z6fkxUwUGP4tw43TkyqE+XiWflyamfvLKNOlycj9gKvOjmH5swX89TeaNCfk9JG93uHZ7zT2XBx8bFmRy5zazj2hmSD5+TCYIA/eh7iMFzdguMrfygLLpt7MwDG6xY=

按道理来这里的结果应该是这样的,但是没有第一个'M'字符,这个可以理解,在ida中可以看到对于前缀不同的地方做了不同的处理,其中就有'M'的分支,但是    'LWVhswVAEjWdmSR3ypZ1P' 只有这里对上了 ,那我们只能去实现解密了看看哪里不一样了


只能解密前面的数据,不能往后了。那是哪里错了,能解密肯定AES的key是对的,base64也没错,那只能是模式出错了,难道还是CBC吗,但是为什么是CBC解密又可以在没有IV的情况下直接把明文给解出来???

IV是异或操作,假如真的考虑是CBC,那么只能是异或之后的结果还是明文了,那只能IV是16字节的0了。所以我们去看看


厉害的,IV是16字节的0。

这里就可以看到了,前面就差了一个"M"了

至于最后,为什么会因为这里CBC模型的AES,而且默认的填充方式是PKCS7,但是结果看到的却是NO padding的0填充,在如画的文章里面说是因为修改r0为指向新字符串的新指针有很大关系,导致的大概是指向地址不同了,并且使用了nopadding对于数据加密,也会得到hello的结果是最终的结果。

这里我们恢复了之前传入的参数,看了看再padding之后的内存存储的数据是什么


其实是可以看到这里的数据是有304个字节的,其中在最后一个16字节的块中是填充了 0c 的字节的,其实能够看到的是这里就是PKCS7的填充方式。这里的AES的KEY和IV都有了,算法已经是可以复现了。本来想着不贴如画佬的复现代码了,但是还是贴上去了。

import base64
from Crypto.Cipher import AES
import requests
import hashlib
from Crypto.Util.Padding import unpad

def __pkcs7padding(plaintext):
    block_size = 16
    text_length = len(plaintext)
    bytes_length = len(plaintext.encode('utf-8'))
    len_plaintext = text_length if (bytes_length == text_length) else bytes_length
    return plaintext + chr(block_size - len_plaintext % block_size) * (block_size - len_plaintext % block_size)
def aes_encrypt(mobile,password):
    _str = f'mobile={mobile}&password={password}&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=eu4acofTmb&response_type=token&ostype=ios&imei=unknown&mac=02:00:00:00:00:00&model=Pixel 4 XL&sdk=29&serviceTime=1709100421650&mod=Google'
    checkcode = hashlib.md5(_str.encode()).hexdigest()
    swapped_string = checkcode[24:] + checkcode[8:24] + checkcode[:8]
    plaintext = _str+'&checkcode='+swapped_string
    key = bytes.fromhex('F6F472F595B511EA9237685B35A8F866')
    iv = bytes.fromhex('00000000000000000000000000000000')
    aes = AES.new(key, AES.MODE_CBC, iv)
    content_padding = __pkcs7padding(plaintext)  # 处理明文, 填充方式
    encrypt_bytes = aes.encrypt(content_padding.encode('utf-8'))  # 加密
    return 'M' + str(base64.b64encode(encrypt_bytes), encoding='utf-8')  # 重新编码
def decrypt(text):
    ciphertext = base64.b64decode(text)
    key = bytes.fromhex('F6F472F595B511EA9237685B35A8F866')
    iv = bytes.fromhex('00000000000000000000000000000000')
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)
    decrypted_data = unpad(plaintext, AES.block_size, style='pkcs7')
    return decrypted_data.decode("utf-8")
def login():
    headers = {
        "channel": "yingyongbao",
        "platformNo": "Android",
        "appVersionCode": "1481",
        "version": "V8.0.14",
        "imei": "a-759f0c27ef7fe3b6",
        "imsi": "unknown",
        "deviceModel": "Pixel 4",
        "deviceBrand": "google",
        "deviceType": "Android",
        "accessChannel": "1",
        # "oauthConsumerKey": "2019041810222516127",
        "timestamp": "1709100421649",
        "nonce": "PCpLXbXts7",
        "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
        "Host": "api.00bang.cn",
        "User-Agent": "okhttp/4.9.0"
    }
    url = "https://api.00bang.cn/llb/oauth/llb/ucenter/login"
    mobile = ''  # 换成你自己的
    password = '' # 换成你自己的
    sd = aes_encrypt(mobile,password)
    print(sd)
    data = {
        "sd": sd
    }
    response = requests.post(url, headers=headers, data=data,verify=False)
    print('加密结果:',response.text)
    print(response)
    print('解密结果',decrypt(response.json()['sd'][1:]))
if __name__ == '__main__':
    login()

本文章中所有内容仅供学习交流使用,不用于其他任何目的,擅自使用本文讲解的技术而导致的任何意外,与作者不负责



通过网盘分享的文件:llb.apk
链接: https://pan.baidu.com/s/1YPf2yyC-nFvxRKsweiNVGg?pwd=vbvm 提取码: vbvm
--来自百度网盘超级会员v4的分享

image-20241222142714222.png (282.8 KB, 下载次数: 3)

image-20241222142714222.png

image-20241222185642956.png (55.08 KB, 下载次数: 2)

image-20241222185642956.png

image-20241222185855219.png (57.02 KB, 下载次数: 1)

image-20241222185855219.png

image-20241222190209676.png (427.92 KB, 下载次数: 2)

image-20241222190209676.png

image-20241222191051888.png (293.6 KB, 下载次数: 1)

image-20241222191051888.png

image-20241222191143803.png (872.19 KB, 下载次数: 2)

image-20241222191143803.png

image-20241222142937056.png (9.7 KB, 下载次数: 1)

image-20241222142937056.png

image-20241222185855219.png (57.02 KB, 下载次数: 1)

image-20241222185855219.png

image-20241222142604809.png (45.22 KB, 下载次数: 1)

image-20241222142604809.png

image-20241222142213672.png (23.17 KB, 下载次数: 1)

image-20241222142213672.png

image-20241222133050631.png (996.09 KB, 下载次数: 1)

image-20241222133050631.png

image-20241223115258497.png (998.57 KB, 下载次数: 1)

image-20241223115258497.png

image-20241225172334928.png (195.87 KB, 下载次数: 1)

image-20241225172334928.png

image-20241225175512662.png (49.47 KB, 下载次数: 1)

image-20241225175512662.png

image-20241225175530729.png (171.3 KB, 下载次数: 1)

image-20241225175530729.png

image-20241225175152016.png (27.13 KB, 下载次数: 1)

image-20241225175152016.png

免费评分

参与人数 17吾爱币 +16 热心值 +16 收起 理由
wolf224 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
beihai1314 + 1 + 1 我很赞同!
arctan1 + 1 + 1 我很赞同!
swiftR + 1 + 1 谢谢@Thanks!
ioyr5995 + 1 + 1 我很赞同!
liyitong + 1 + 1 我很赞同!
snoopylike + 1 用心讨论,共获提升!
xuzhao0320 + 1 + 1 用心讨论,共获提升!
Ruomeng + 1 谢谢@Thanks!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
yuaotian + 1 + 1 用心讨论,共获提升!
ikkz + 1 + 1 用心讨论,共获提升!
hc6125322 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
midkr + 1 + 1 谢谢@Thanks!
bGl1 + 1 + 1 我很赞同!
Hawcett + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pengge123 + 1 + 1 鼓励转贴优秀软件安全工具和文档!

查看全部评分

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

来自 2#
 楼主| chenchenchen777 发表于 2024-12-26 19:06 |楼主
通过网盘分享的文件:llb.apk
链接: https://pan.baidu.com/s/1YPf2yyC-nFvxRKsweiNVGg?pwd=vbvm 提取码: vbvm
--来自百度网盘超级会员v4的分享
4#
Thank友 发表于 2024-12-26 14:03
5#
lovejing0331 发表于 2024-12-26 14:13
6#
dingqh 发表于 2024-12-26 15:32
放马过来吧,我现在强的可怕
7#
icexran 发表于 2024-12-26 16:06
确实看不懂,大神流弊
8#
T2ao 发表于 2024-12-26 16:06
膜拜大佬,好好学习天天向上
9#
wushengli 发表于 2024-12-26 16:07
什么意思没看懂介绍下
10#
lyn1875 发表于 2024-12-26 18:05
感谢分享
11#
rl910407 发表于 2024-12-26 19:25
不明觉厉,学习学习。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2025-1-1 19:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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