• 白盒AES和SM4实现的差分故障分析


    DFA攻击背景介绍#

    传统的密码安全性分析环境被称为黑盒攻击环境,攻击者只能访问密码系统的输入与输出,但随着密码系统部署环境的多样化,该分析模型已经不能够反映实际应用中攻击者的能力。2002年,Chow等人[1]提出了白盒攻击环境的概念,该攻击环境中的攻击者对算法运行环境具备完全的控制权,并且完全掌握算法的设计细节。白盒攻击环境中攻击者的能力包括但不限于:动态观测算法程序运行过程、修改算法程序运行过程中的中间值、对算法程序进行调试分析等,其中包括了差分计算分析(DCA)。
    差分计算分析(DCA):DCA不需要攻击者掌握算法的设计细节,只需要采集算法程序在运行过程的中间状态,通过相应的统计分析方法提取密钥。DCA分析只需要掌握白盒实现的底层算法,能够监测白盒实现程序的运行过程即可进行分析,极大地降低了分析的部署难度。
    与DCA相似的,Sanfelix,Mune和de Haas在BlackHat Europe 2015上成功提出了针对相同白盒挑战的差分故障分析(DFA)攻击,也就是我们今天要研究的差分故障分析攻击。在大部分白盒攻击模型中,故障非常容易执行且成本低廉,并且不会导致程序自我毁灭

    AES算法介绍#

    AES是著名的非对称算法,通过每轮变换的轮密钥实现加密。它由 10、12 或 14 轮(分别用于 AES-128、AES-192 和 AES-256)组成)通过重复操作逐步转换 16 字节输入。在AES-128里,第一轮的密钥就是AES的密钥,而在AES-256里,第一轮的密钥被拆分为两个轮密钥。
    而问题就出在这里,在白盒里面,密钥不会从原地进行轮密钥加,而是在生成白盒时预先计算的,而且轮密钥加会混合在其他轮次中来进行隐藏。
    AES的整个加密解密流程如下图
    111.png
    AddRoundKey (轮密钥加)— 矩阵中的每一个字节都与该次轮密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
    **SubBytes(字节替代) **— 通过非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
    **ShiftRows(行移位) **— 将矩阵中的每个横列进行循环式移位。
    MixColumns (列混淆)— 为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每列的四个字节。

    DFA分析AES-128加密#

    DFA 攻击的一般要求是:

    • 输出必须在没有外部编码的情况下可观察(直接在密码末尾或解码后在应用程序中的其他地方)。
    • 必须能够在同一输入(可以是编码的或未知的)上多次执行密码。在几轮后捕获 AES 的状态并多次恢复它也是一种选择。

    由AES加密算法流程可以看出,第10次轮秘钥加之前是没有列混淆的。
    所以我们可以知道第九次轮密钥加之前是这样的:
    333.png
    如果我们在第九轮列混淆之前构造如下两组数据:
    222.png
    可以看出这两组数据只有第一个数据不一样,以当前状态继续进行,那么作为状态矩阵的输入就会影响到其余地方,由于444.png
    通过上述表达式推算,可以得到一组K10的(0,7,10,13)的位置值,同理,如果换成其他位置的值,可以得到K10的其他位置值,从而推算得到整个K10的值,再根据AES秘钥拓展算法(),最终可以还原原始的加密秘钥。

    实战#

    2023巅峰极客逆向 m1_read#

    ida打开附件,findcrypto一下发现很明显的AES加密
    555.png
    但是找了很久都没有找到密钥,于是可以联想到是白盒AES(别问怎么联想的),用大爹的模拟执行代码进行还原密钥
    我们借用qiling框架来模拟程序执行。

    参数传入#

    函数开头传⼊参数, rcx 存储输⼊的地址

    Copy
    def hook_args(ql: Qiling): ql.mem.write(0x500000000, b"\x01" * 16) ql.arch.regs.write("rcx", 0x500000000) #print(ql.mem.read(0x500000000, 16)) ql.mem.write(0x500000000 + 0x10, b"\x00" * 16) ql.arch.regs.write("rdx", 0x500000000 + 0x10) ql.mem.write(0x500000000 + 0x20, b"\x00" * 16) ql.arch.regs.write("rbx", 0x500000000 + 0x20) #print("Hook Success") return start_addr = 0x140004BF0 ql.hook_address(hook_args, start_addr)

    找到插入缺陷数据的地方#

    上文中,我们说过要将构造多组数据将AES的密钥攻击出来,而我们是在第九次进行轮密钥加之前加入的这种数据,这种数据后文叫缺陷数据。
    找到列混合的地方可以很快找到轮密钥加,在本题中我们很容易找到一个对16位字符进行操作的函数,可以肯定这就是列混合的地方。
    777.png
    往下翻,就能找到轮密钥加的地方
    888.png
    每轮v6加了1024*4个字节,也就是1000个字节,在经过八次相加之后就是8000,在这之后加入缺陷代码
    999.png
    在汇编层面上就是下面的代码
    101.png

    Copy
    def hookcode(ql:Qiling): if ql.arch.regs.read("r12") == 0x8000: global index #第一次需要获取正确的密文,所以不需要插入\x00,也就是说下面的write第一次不用写 ql.mem.write(0x500000000 + index,b'\x00') index += 1 #此处是获取正确的密文 #print(ql.mem.read(0x500000000,16).hex()) #print(ql.mem.read(0x500000000,16)) return index_addr = 0x1400052c5#这个地址就是判断是否到第酒次的地址 ql.hook_address(hookcode,index_addr)

    获取密文#

    找到定义密文的地方,如下图所示
    image.png
    image.png
    所以可以直接hook这个地址,拿到密文

    Copy
    def hook_enc(ql:Qiling): print(ql.mem.read(0x500000000,16).hex) return enc_addr = 0x1400053ca ql.hook_addr(hook_enc,enc_addr)

    运行两次(每次的运行代码不同)验证正确密文和错误密文,看结果是否满足白盒AES原理
    image.png
    可以看出,分别得到了正确密文为

    Copy
    e14d5d0ee27715df08b4152ba23da8e0

    第九轮列混淆时,错误的密文为

    Copy
    d24d5d0ee27715ac08b4bf2ba272a8e0

    在第0,7,10,13个字节分别与正确密文有错误,和原理一致。

    获取所有错误密文#

    写个for循环即可得到剩下的密文,整个代码如下

    Copy
    from qiling import * from qiling.const import QL_VERBOSE index = 0 ql = Qiling( ["/home/nian/桌面/examples/m1_read.exe"], r"/home/nian/桌面/examples/rootfs/x86_windows", verbose=QL_VERBOSE.OFF, ) def hook_args(ql: Qiling): ql.mem.write(0x500000000, b"\x01" * 16) ql.arch.regs.write("rcx", 0x500000000) #print(ql.mem.read(0x500000000, 16)) ql.mem.write(0x500000000 + 0x10, b"\x00" * 16) ql.arch.regs.write("rdx", 0x500000000 + 0x10) ql.mem.write(0x500000000 + 0x20, b"\x00" * 16) ql.arch.regs.write("rbx", 0x500000000 + 0x20) #print("Hook Success") return def hook_code(ql: Qiling): if ql.arch.regs.read("r12") == 0x8000: global index ql.mem.write(0x500000000 + index, b"\x00") index += 1 #print(ql.mem.read(0x500000000, 16).hex()) #print(ql.mem.read(0x500000000 + 0x10, 16)) return def hook_enc(ql: Qiling): print(ql.mem.read(0x500000000, 16).hex()) return index_addr = 0x1400052C5 start_addr = 0x140004BF0 end_addr = 0x14000542D enc_after = 0x1400053CA ql.hook_address(hook_args, start_addr) ql.hook_address(hook_code, index_addr) ql.hook_address(hook_enc, enc_after) # e14d5d0ee27715df08b4152ba23da8e0 # e14d5d73e27708df0878152b843da8e0 for i in range(16): ql.run(begin=start_addr, end=end_addr)

    得到如下错误密文
    image.png

    Copy
    """d24d5d0ee27715ac08b4bf2ba272a8e0 e14d5d73e27708df0878152b843da8e0 e14dd50ee23415df7fb4152ba23da890 e16f5d0e537715df08b415e7a23dc6e0 e11a5d0e057715df08b4151ba23d99e0 574d5d0ee277157508b4df2ba234a8e0 e14d5d49e27785df0840152bff3da8e0 e14db80ee2d215dfceb4152ba23da868 e14dc60ee2bf15dfc4b4152ba23da8bf e1425d0e5e7715df08b415b6a23d4ce0 5d4d5d0ee277159608b42f2ba297a8e0 e14d5d6ce2773ddf089d152ba93da8e0 e14d5dcde2772adf084b152bba3da8e0 e14df40ee27115df96b4152ba23da881 e11b5d0e337715df08b41544a23df3e0 fa4d5d0ee27715af08b42e2ba2c2a8e0"""

    还原第十个密钥#

    现在已知了正确的密文和所有的错误密文,可以用phoenixAES工具来还原原理中提到的k10
    https://github.com/SideChannelMarvels/JeanGrey/tree/master/phoenixAES
    代码如下:

    Copy
    import phoenixAES with open("tracefile","wb") as t: t.write( """ e14d5d0ee27715df08b4152ba23da8e0 d24d5d0ee27715ac08b4bf2ba272a8e0 e14d5d73e27708df0878152b843da8e0 e14dd50ee23415df7fb4152ba23da890 e16f5d0e537715df08b415e7a23dc6e0 e11a5d0e057715df08b4151ba23d99e0 574d5d0ee277157508b4df2ba234a8e0 e14d5d49e27785df0840152bff3da8e0 e14db80ee2d215dfceb4152ba23da868 e14dc60ee2bf15dfc4b4152ba23da8bf e1425d0e5e7715df08b415b6a23d4ce0 5d4d5d0ee277159608b42f2ba297a8e0 e14d5d6ce2773ddf089d152ba93da8e0 e14d5dcde2772adf084b152bba3da8e0 e14df40ee27115df96b4152ba23da881 e11b5d0e337715df08b41544a23df3e0 fa4d5d0ee27715af08b42e2ba2c2a8e0 """.encode("utf-8") ) phoenixAES.crack_file("tracefile",verbose=0)

    image.png
    运行后得到k10

    Copy
    """B4EF5BCB3E92E21123E951CF6F8F188E"""

    还原原始密钥#

    得到第十轮的密钥后,就可以着手还原原始密钥了,用到的工具是Stark
    https://github.com/SideChannelMarvels/Stark
    下载好stark之后要记得在文件目录下面make把三个c文件编译成可执行文件,再用可执行文件操作,如下图
    image.png
    这样一来,就得到原始密钥key0了

    Copy
    """00000000000000000000000000000000"""

    再利用原始密钥解密原始密文,即可得到明文

    解密#

    image.png
    out.bin里面的密文

    Copy
    """0B987EF5D94DD679592C4D2FADD4EB89"""

    解密即可得到明文

    Copy
    from Crypto.Cipher import AES enc = bytearray(bytes.fromhex("0B 98 7E F5 D9 4D D6 79 59 2C 4D 2F AD D4 EB 89")) enc = bytes([enc[i] ^ 0x66 for i in range(16)]) key = bytes.fromhex("00000000000000000000000000000000") aes = AES.new(key=key, mode=AES.MODE_ECB) print(aes.decrypt(enc))

    运行,得到flag
    image.png

    上述就是基本的AES差分故障分析方法和例题,而除了AES之外,还有DES和SM4的白盒,它们和AES的故障攻击方法基本大同小异
    这里给出研究SM4的博客供大家(wo)学习
    https://www.anquanke.com/post/id/231483

  • 相关阅读:
    2022-09-09 Unity InputSystem2——代码检测输入
    既然有 HTTP 协议,为什么还要有 RPC
    MS SQL Server partition by 函数实战 统计与输出
    强化科技创新“辐射力”,中国移动的数智化大棋局
    Zotero详细功能补充!熟练使用!【进阶版,持续更新】
    三维重建_表面重建_基于符号距离场的表面重建
    Guava 对 Map的操作
    图解kd树+Python实现
    【图像处理OpenCV(C++版)】——初学OpenCV
    科技型中小企业认定条件和好处
  • 原文地址:https://www.cnblogs.com/fuxuqiannian/p/17679058.html