首页 > 其他分享 >使用frida分析白盒aes,DFA攻击

使用frida分析白盒aes,DFA攻击

时间:2024-12-21 23:09:28浏览次数:4  
标签:function aes console log 白盒 args result var frida

这次分析的app是:五菱汽车(8.2.1)

登录,抓包

发现请求体只有sd字段,看见加密的时候,可以先使用算法助手hook java层所有加解密方法

发现我们所需要的sd加密字段在java层hook不到,那加密算法应该是写在了so层,因为这个app是bb加固企业,得有脱壳机才能脱。

jadx加载dex,直接搜"sd"

发现这里有个变量定义,直接跟进

这里貌似是实现了post字段的装填,我们可以frida hook这个方法看看

function hook_addCheckcode(){
    Java.perform(function(){
        let CheckCodeUtils = Java.use("com.cloudy.linglingbang.model.request.retrofit2.CheckCodeUtils");
        CheckCodeUtils["addCheckCode"].implementation = function (str, i) {
            console.log(`CheckCodeUtils.addCheckCode is called: str=${str}, i=${i}`);
            let result = this["addCheckCode"](str, i);
            console.log(`CheckCodeUtils.addCheckCode result=${result}`);
            return result;
        };
    })
}

发现确实hook到了登录数据

** 随后跟进encrypt方法**

继续跟进

最后跟进到checkcode方法,这个含有三个参数的checkcode是写在了native层,hook看一下

没问题,查看这个方法是在so库中

直接ida打开这个so文件

一开始可以现在搜索框输入java,看看方法是不是静态注册,如何是动态注册的方法,可以hook registernatives函数进行查看偏移地址和分析,脚本如下:


var ENV = null;
var JCLZ = null;

var method01addr = null;
var method02addr = null;
var method02 = null;

var addrNewStringUTF = null;

var NewStringUTF = null;


function hook_RegisterNatives() {
  var symbols = Module.enumerateSymbolsSync("libart.so");
  var addrRegisterNatives = null;
  for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols[i];

    //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 && 
        symbol.name.indexOf("NewStringUTF") >= 0 && 
        symbol.name.indexOf("CheckJNI") < 0) {
      addrNewStringUTF = symbol.address;
      console.log("NewStringUTF is at ", symbol.address, symbol.name);
      NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
    }
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 && 
        symbol.name.indexOf("RegisterNatives") >= 0 && 
        symbol.name.indexOf("CheckJNI") < 0) {
      addrRegisterNatives = symbol.address;
      console.log("RegisterNatives is at ", symbol.address, symbol.name);
    }
  }

  if (addrRegisterNatives != null) {
    Interceptor.attach(addrRegisterNatives, {
      onEnter: function (args) {
        console.log("[RegisterNatives] method_count:", args[3]);
        var env = args[0];
        ENV = args[0];
        var java_class = args[1];
        JCLZ = args[1];
        var class_name = Java.vm.tryGetEnv().getClassName(java_class);
        //console.log(class_name);

        var methods_ptr = ptr(args[2]);

        var method_count = parseInt(args[3]);
        for (var i = 0; i < method_count; i++) {
          var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
          var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
          var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

          var name = Memory.readCString(name_ptr);
          var sig = Memory.readCString(sig_ptr);
          var find_module = Process.findModuleByAddress(fnPtr_ptr);
          console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
          if(name.indexOf("method01")>=0){
            // method01addr = fnPtr_ptr;
            continue;
          }else if (name.indexOf("decrypt")>=0){
            method02addr = fnPtr_ptr;
            method02 = new NativeFunction(method02addr,'pointer',['pointer','pointer','pointer']);
                        method01addr = Module.findExportByName("libroysue.so", "Java_com_roysue_easyso1_MainActivity_method01")
                    }else{
                        continue;
                    }

                }
            }
        });
    }
}


function invokemethod01(contents){
    
    console.log("method01_addr is =>",method01addr)
    var method01 = new NativeFunction(method01addr,'pointer',['pointer','pointer','pointer']);
    var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer'])
    var result = null;
    Java.perform(function(){    
        console.log("Java.vm.getEnv()",Java.vm.getEnv())
        var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
        result = method01(Java.vm.getEnv(),JSTRING,JSTRING);
        console.log("result is =>",result)
        console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
        result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();

    })
    return result;
}

function invokemethod02(contents){
    var result = null;
    Java.perform(function(){    
        var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(contents))
        result = method02(Java.vm.getEnv(),JSTRING,JSTRING);
        result = Java.vm.getEnv().getStringUtfChars(result, null).readCString();
    })
    return result;
}
rpc.exports = {
    invoke1:invokemethod01,
    invoke2:invokemethod02
};

setImmediate(hook_RegisterNatives);


/*
java_class: com.example.demoso1.MainActivity name: method01 sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x73e2cd1018 module_name: libnative-lib.so module_base: 0x73e2cc1000 offset: 0x10018
java_class: com.example.demoso1.MainActivity name: method02 sig: (Ljava/lang/String;)Ljava/lang/String; fnPtr: 0x73e2cd0efc module_name: libnative-lib.so module_base: 0x73e2cc1000 offset: 0xfefc

function hookmethod(addr){
    Interceptor.attach(addr,{
        onEnter:function(args){
            console.log("args[0]=>",args[0])
            console.log("args[1]=>",args[1])
            console.log("args[2]=>",Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
        },onLeave:function(retval){
            console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
        }
    })
}


function replacehook(addr){
    //> 能够hook上,就能主动调用
    var addrfunc = new NativeFunction(addr,'pointer',['pointer','pointer','pointer']);
    Interceptor.replace(addr,new NativeCallback(function(arg1,arg2,arg3){
        // 确定主动调用可以成功,只要参数合法,地址正确
        var result = addrfunc(arg1,arg2,arg3)
        console.log(arg1,arg2,arg3)
        console.log("result is ",Java.vm.getEnv().getStringUtfChars(result, null).readCString())
        return result;
    },'pointer',['pointer','pointer','pointer']))
}


*/

因为这里是静态注册,我们直接查看该函数就行

点击该方法,先更改一下传参变量名,第一个类型是JNIEnv*的指针变量,改完以后,后面JNI函数会比较好分析

我们发现,点击来这个函数的时候,消耗了挺多的时间,我们看一下汇编,

可以发现是加了ollvm混淆,基于这个混淆效果不是很强,我们可以直接关注关键函数进行分析

往下看一下代码,发现有个aes_encrypy1和aes_encrypt2

点进去aes_encrypy1

是一个函数,有三个参数,我们可以来hook看这三个参数到底是什么东西,我们现在java层写一个checkcode方法的主动调用,这样后面分析就不用频繁登录

function call(){
    Java.perform(function () {
        let CheckCodeUtils = Java.use("com.cloudy.linglingbang.model.request.retrofit2.CheckCodeUtils");
        let instanc = CheckCodeUtils.$new();
        let result = instanc.encrypt("abcdefghij", 1);
        console.log(`CheckCodeUtils.encrypt result=${result}`);
    })
}

这样我们在frida界面输入call()就可以调用一次加密

好的,现在开始hook native层的函数,代码如下

先找到函数偏移先,然后直接hook

function hook_aesencry1(){
    let baseaddr = Module.findBaseAddress('libencrypt.so');  
    Interceptor.attach(baseaddr.add(0xA5BC), {
        onEnter:function(args){
            console.log('aes_encry1 is called');
            console.log('args[0]:'+ hexdump(args[0]));
            console.log('args[1]:'+ hexdump(args[1]));
            console.log('args[2]:'+ hexdump(args[2]));

        },
        onLeave:function(retval){

        }

    })
}   

可以发现ase_encrypy1被调用,第一个参数就是我们输入的字符串,说明加密应该是这里

跟进WBACRAES128_EncryptCBC

分析一下代码,可以看到这个函数有两个主要方法

InsertCBCPadding,这个判断是aes加密的填充函数

还有这个WBACRAES_EncryptOneBlock,跟进

根据上面代码分析,这个this指针应该是我们传入的字符串,我们可以在EncryptOneBlock这里hook看一下,这里就不放代码,可以发现就是我们传入的字符串,我们继续跟进

可以发现出现了一点小问题,我们看一下汇编代码

主要看一下这个BLR指令,这是一个跳转指令,跳转地址是在x4这个寄存器里面,我们现在是看不懂x4寄存器内容,可以hook这个地址,使用frida的上下文查看x4寄存器的地址,代码如下

function hook_x4(){
    let baseaddr = Module.findBaseAddress('libencrypt.so');
    let targetaddr = baseaddr.add(0xA03C);

    Interceptor.attach(targetaddr, {
        onEnter:function(args){
            console.log("寄存器的值: " + this.context.x4);
        },
        onLeave:function(retval){
        }

    })
}

这里我们先hook_x4()挂钩先,然后主动调用call()就行

x4的地址为0x7cea2086f8,ida里面的偏移就是86f8,我们直接按G跳转该位置

成功进入到该函数内部,还是一样,先hook这四个参数看看

可以发现第二个参数就是我们传入的字符串,这里对他进行命名为indata,我们接着分析代码

这里出现了PrepareAESMatrix这个函数,参数是我们输入的字符串,其他几个参数我们暂时不知道,跟进这个函数看看

这里看着像是对明文的一些操作,我们hook一下看一下参数,第三个参数需要在函数返回时进行hook

function hook_peraes(){
    let baseaddr = Module.findBaseAddress('libencrypt.so');
    let targetaddr = baseaddr.add(0x7874);
    let args2= null
   
    Interceptor.attach(targetaddr, {
        onEnter:function(args){
            // console.log('args[0]:'+ hexdump(args[0]));
            // console.log('args[1]:'+ hexdump(args[1]));
            // console.log('args[2]:'+ hexdump(args[2]));
            args2 = args[2];
              

        },
        onLeave:function(retval){
            console.log('处理后的结果' + hexdump(args2));
            
        }
    })
}

确实是对明文进行处理,这里先命名为indata_state,aes加密就是使用对明文进行处理,然后与密钥处理后的结果进行十轮加密,十轮加密都会对indata_state进行修改,因为没找到分析密钥,分析应该是白盒aes加密

白盒AES是一种将密钥嵌入到加密算法中的技术,使得即使攻击者能够完全访问加密算法的执行过程,也无法轻易提取出密钥。这种技术主要用于防止逆向工程和密钥泄露。

网上对白盒aes加密的分析很多,这里主要是分析crack的过程

DFA故障攻击是一种目前处理白盒aes加密的主流方法,过程就是在进行加密是修改indata_state的内容,注入随机字节,原理网上也很多,这里不在赘述,我们回到代码

可以发现,indata_state确实在被修改处理,我们现在要找到没有列混淆的最后一轮之前之前注入就行

可以发现这里有个特征值,int10 ==10,往前看代码

int10是a4赋值,也是这个函数的第四个参数,我们看一下第四个参数是什么

可以发现第四个参数是0xa,也就是数字10,这里判断应该就是加密轮次

再看v9的值

初始被赋值为0,这里可以看一下v9到底被++了多少次,

查看汇编

这里w20存的就是v9的值,我们可以在hook偏移8970,查看寄存器的值,判断到底cmp了多少次

代码如下

function hook_w20(num){
    let baseaddr = Module.findBaseAddress('libencrypt.so');
    let targetaddr = baseaddr.add(0x8970);
    Interceptor.attach(targetaddr, {
        onEnter:function(args){
            console.log("寄存器w20的值为====>>>", this.context.x20);
        },
        onLeave:function(retval){
            
        }
    })
}

可以发现这里应该进行了9轮加密,符合aes加密轮数,那我们在进入第9轮时,更改indata_state的内容,注入故障文就行,(对于10轮函数的AES-128,DFA攻击通常将最后两轮设为目标。当故障注入到最后2轮中时,密文的某一些字节会受到故障的影响。)这里为了方便查看,call()主动调用采用16字节"0123456789abcdef"

function call(){
    Java.perform(function () {
        let CheckCodeUtils = Java.use("com.cloudy.linglingbang.model.request.retrofit2.CheckCodeUtils");
        let instanc = CheckCodeUtils.$new();
        let result = instanc.encrypt("0123456789abcdef", 1);
        console.log(`CheckCodeUtils.encrypt result=${result}`);
    })
}

然后我们直接写出代码

let num = null;
function hook_change_state(){
    let baseaddr = Module.findBaseAddress('libencrypt.so');
    let data_state = null;   //明文加密后的地址,故障文注入地址
    let w20_addr  = baseaddr.add(0x8970);
    let aes_result = null; //aes加密结果

    Interceptor.attach(baseaddr.add(0x86f8), {
        onEnter:function(args){
            aes_result = args[2]           
        },
        onLeave:function(retval){
            //console.log("aes加密结果为====>>>>>>",hexdump(aes_result))
            let aes_data_to_print = Memory.readByteArray(aes_result, 16); 
                    // 将字节数组转换为十六进制字符串
            let hexString = Array.from(new Uint8Array(aes_data_to_print))
            .map(b => ('0' + b.toString(16)).slice(-2)) // 转换为十六进制,确保两位
            .join('');

            console.log("aes加密结果为====>>>>>>", hexString);
            
        }

    })

    Interceptor.attach(baseaddr.add(0x7874), {
        onEnter:function(args){
            //console.log("args[2]===>>>", hexdump(args[2]));
            data_state = args[2];
        },
        onLeave:function(retval){
            //console.log("state_addr==>>", hexdump(data_state));
        }
    })

    Interceptor.attach(w20_addr, {
        onEnter:function(args){
            if(this.context.x20 == 0x8){
               //console.log("state_data=====>>>>>>>>>>>>>>>>>>>>>>",hexdump(data_state));
               Memory.writeByteArray(data_state, [num])
               //console.log("修改的state如下:"+hexdump(data_state))
            }
        },
        onLeave:function(retval){
            
        }
    })
}

function hook_call(){
    let nums=[0x39,0x20,0x36,0x72,0x27]
    hook_change_state()
    for (let i = 0; i < nums.length; i++) {
        num = nums[i]
        //console.log("num====>>>", num)
        call()
        console.log("================================================")
    }
}

我们先回到之前indata_state,位置看看改成16字节的输入,indata_state是怎么样

我们在这里四个字节分别修改五次,总共20次,修改内存的数据使frida Memory.writeByteArray(data_state, [num])api进行修改

第二个字节的修改Memory.writeByteArray(data_state.add(0x1), [num])就行,以此类推

修改20次故障文后aes加密结果

9a39f8f250d9a12988803093cefe4f80
9139f8f250d9a1d988806293ce944f80
c639f8f250d9a19e88800d93ce154f80
1539f8f250d9a13588804c93ce984f80
9739f8f250d9a10288802593ce874f80
50c4f8f2b0d9a106888065e2cefc1280
508bf8f257d9a1068880655dcefc4980
50b5f8f202d9a106888065cdcefc9980
50d2f8f266d9a1068880651dcefc0c80
5015f8f243d9a10688806598cefc8580
50392bf2505ba106de806593cefc4f73
50393df2501da1060f806593cefc4fdb
5039b2f25020a106e6806593cefc4f6c
503999f250e7a10667806593cefc4f02
5039b4f250dda106da806593cefc4f0b
5039f81b50d97f0688a865939dfc4f80
5039f8e250d9270688ea6593abfc4f80
5039f85450d92106887d65938ffc4f80
5039f8ed50d9170688ad65934afc4f80
5039f89250d9f00688c36593f2fc4f80

然后使用phoenixAES还原出第十轮的密钥

import phoenixAES

phoenixAES.crack_file('crackfile', [], True, False, verbose=2)

crackfile只需要在上述故障文第一行加入正常的数据就行

第十轮密钥是8A6E30D74045AE83634D6ECDE1516CA1

接着使用https://github.com/SideChannelMarvels/Stark,编译aes_keyschedule.c

还原出密钥

密钥是8A6E30D74045AE83634D6ECDE1516CA1,根据上面明文的处理,indata_state数据其实没有改变,可判断iv其实就是0,因为明文需要与iv先异或,iv填充16个0就行,至此分析完毕,加密后base64就行




标签:function,aes,console,log,白盒,args,result,var,frida
From: https://www.cnblogs.com/GGbomb/p/18621516

相关文章

  • 白盒测试也称为结构测试、透明盒测试、逻辑驱动测试或基于代码的测试
    白盒测试概述白盒测试也称为结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。它是一种测试用例设计方法,测试人员可以访问程序的内部结构,如代码和逻辑流程。在Java程序中,这意味着能够查看类、方法、控制结构(如条件语句和循环)等内容。主要目的是通过检查程序的内部逻辑结......
  • 灰盒测试是一种软件测试方法,它介于黑盒测试和白盒测试之间。
    一、概念黑盒测试是把软件看作一个不透明的黑盒子,只关注软件的输入和输出,而不关心内部的逻辑结构。白盒测试则相反,测试人员可以看到软件内部的代码逻辑结构,根据程序内部的逻辑来设计测试用例。灰盒测试结合了两者的特点,测试人员对软件内部的部分实现细节有所了解,但并不需要像白......
  • 渗透测试-前端加密之AES加密下的SQL注入
    本文是高级前端加解密与验签实战的第4篇文章,本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过前端AES加密进行SQL注入。CryptoJS.AES-被前端加密的SQL注入绕过SQL注入前端代码和上文一样,都是通过AES加密请求的数据。在yaklang\common\vulinbox\db.go中可以看......
  • 从frida到va_list
    背景在用fridahookCallStaticVoidMethodV函数参数的时候,发现va_list结构有些复杂,不好hook,故进行了学习va_list介绍va_list是在c语言中解决变参问题的一组宏。va_list的内部结构由编译器与平台架构决定,不同组合可能会导致va_list的定义和行为存在显著差异。在x86_64架......
  • AES加密文件后附加到图片后面传输
    加密过程为:1、将文档压缩成zip;2、将zip字节流用aes加密;3、将加密后的字节流附加到图片后面。解密流程为:1、从图片后面取出加密后的字节流;2、使用aes解密出zip数据;3、解压zip。importzipfilefrompathlibimportPathfromCrypto.CipherimportAESfromCrypto.Util.Padding......
  • 【密码学】AES算法
    一、AES算法介绍:AES(AdvancedEncryptionStandard)算法是一种广泛使用的对称密钥加密,由美国国家标准与技术研究院(NIST)于2001年发布。AES是一种分组密码,支持128位、192位和256位三种不同的密钥长度。AES的分组大小固定为128位,这意味着每次处理128位的数据块。AES算法的核心......
  • jmeter AES加密/解密
    首先了解一下,什么是AES加密/解密?AES(全称:AdvancedEncryptionStandard)对称加密算法,也就是加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合,如:数据加密存储、网络通信加密等。在进行接口测试或接口压测时,有些比较核心的接口有可能会用AES方式对......
  • window.crypto.subtle 实现AES-128对称加密算法
    window.crypto.subtle支持AES-128对称加密算法。AES(高级加密标准)是一种广泛使用的对称加密算法,它有三种密钥长度:128位、192位和256位。在WebCryptoAPI中,你可以选择不同的密钥长度来生成AES密钥。以下是一个使用AES-128-CBC模式的加密和解密示例:asyncfunctiongenerateKey()......
  • 薛定谔Maestro-分子对接另一个软件
    可以网上下载翻版的,但是发文章或者什么使用最好的单位已经购买的,很多软件都有这样的问题,需要了解一下是不是开源的这个太久没有用都有点忘记了,需要翻开笔记一点点对一下然后开始记录纸质的笔记除了实验步骤其他的都不是很方便呀,不够形象,没有图片观看怎么点击我就是傻瓜式的......
  • 【ios逆向】frida 调试iphone,简单环境搭建,脚本运行绕过
    1.首先windows端安装fridapython-mpipinstall--upgradefrida-tools2.iphone端下载对应的frida,越狱后拿ssh传上去https://github.com/frida/frida/releases然后安装.deb文件dpkg-ifrida_16.5.7_iphoneos-arm.deb指定端口运行Max:~root#frida-server--l......