混淆的 AES 解密程序集

逆向工程 部件 混淆 手臂 加密
2021-06-26 23:29:35

我最近一直在筛选整个程序集,以尝试确定程序如何解密某些数据。到目前为止,我已经确定了 IV 是如何提取的,IV 是 16 个字节长,并且解密方法使用密码块链接。因此,我相信所使用的加密方法是 AES-128-CBC。

下一步是尝试识别用于解密的密钥,问题是单个分组密码加密的程序集大小约为 2.5MB。然而,我观察到的是,它都是一个非常相似的形式,例如,一个片段:

add.w      r0, r12, #0x13
str.w      r0, [lr, #0x44]
tst.w      r0, #0xff
mov        r0, r12
it         eq
eoreq      r0, r12, #0x75

add.w      r1, r12, #0x5d
str.w      r1, [sp, #0xf00]
tst.w      r1, #0xff
it         eq
addeq      r0, #0x3b

r12包含从传入参数 ( r0)加载的加密数据,如下所示:

mov        r4, r0
add.w      lr, sp, #0x1000
ldrb.w     r12, [r4]

子程序中的所有程序集都是示例形式,一些偏移量被添加到加密数据中,存储,测试0xff总是 0xff),然后执行一些操作,XOR、OR、ADD 或 MOV 影响另一个寄存器(在示例中是r0)。

我目前的预感是,这可能是一个带有展开轮次的 AES-128 实现。

这对您来说是否是 AES-128,您是否同意加密被故意混淆以隐藏密钥?如果是这样,那么它是如何被混淆的,是否有可能找到密钥?

附加信息

这是块密码加密子程序的完整 ASM 文件的链接

这是一个链接到使用CBC和调用的主要问题引用上面的子程序的子程序。

1个回答

我认为您正确地确定了函数的用途及其参数。因此,该函数sub_44d84有四个参数:(<decryptionFunction=0x1>, <IV>, <bytesToDecrypt>, <decryptedBytes>)sub_46d554使用 CBC 为每个块调用该函数。

sub_46d554函数接收2个参数:

  • R0: 加密输入块
  • R1: 解密输出块

输入块中的字节从函数的第一部分读出,执行解密步骤并写入函数最后一部分的输出缓冲区。

要了解解密功能,首先应该了解混淆的构建块。所以,我在从输入块中读出第二个字节后拿起了一些示例代码(由于编译器优化,函数的开头有点乱)。

在此处输入图片说明

在上图中,标有绿色的线读出输入块的第二个字节,橙色线初始化R2寄存器。黄色和蓝色块执行类似的功能。两个块都以加法开始,将结果存储到局部变量并检查结果是否等于0xFF如果结果是0xFF的值R2已更改。因为R2只有在加法是0xFF并且加法总是使用的时候才会改变LR(它包含输入的第二个字节),所以R2寄存器的值只能改变一次。因为新R2值仅取决于LR这些块实现了一个查找表,这是一个替换框。

[编辑] 查找表是由类似于突出显示的构建块构建的。所以,在伪代码中,查找表的实现方式如下:

if (input == 0xb1) out = 0x5b
if (input == 0x42) out = 0x56
...

为了理解结果存储到局部变量中的原因,让我们检查一下局部变量的下一个用法var_1A8

在此处输入图片说明

它读出作为加法结果的局部变量,并检查它是否0xFF与前面的情况相似。由于该局部变量的值0xFF只有在加法结果0xFF在该值最初存储的位置时才会出现,因此比较会再次检查输入字节是否等于特定值。

如果我们var_1A8在整个函数中搜索局部变量,我们会收到很多带有特定模式 ( STR, LDR, LDR, LDR) 的结果。

在此处输入图片说明

STR意味着附加存储和LDR表示该增加的使用。因此,每个字节都检查四次,然后将使用另一个字节。

通过将所有输入字节用法与局部变量用法进行比较,可以发现以下模式:

i0 = input[0x00]
d0_0 = table0[i0]
id = input[0x0d]
d0_d = table1[id]
ia = input[0x0a]
d0_a = table2[ia]
i7 = input[0x07]
d0_7 = table3[i7]

d1_0 = table4[i0]
d1_d = table5[id]
d1_a = table6[ia]
d1_7 = table7[i7]

d2_0 = table8[i0]
d2_d = table9[id]
d2_a = table10[ia]
d2_7 = table11[i7]

d3_0 = table12[i0]
d3_d = table13[id]
d3_a = table14[ia]
d3_7 = table15[i7]

因此,读取 4 个字节,执行替换并将结果存储在局部变量中。之后,对接下来的 4 个字节执行相同的操作,依此类推。处理完整个块后,读取存储的结果并将一些先前结果的异或值用作新输入,例如:

nb_0 = d0_4 ^ d2_d ^ ...
d0_0 = table16[nb_0]
nb_1 = d1_2 ^ d3_8 ^ ...
d0_1 = table17[nb_1]
...

d1_0 = table20[nb_0]
d1_d = table21[nb_1]
...

通过检查局部变量的使用模式,可以清楚地看到它使用了9轮替换。

基于以上,我认为解密函数可以是 AES-128 实现。因为添加轮键、子字节和移动行步骤操作可以在一个步骤中通过查找表来实现,而在另一个步骤中进行混合列操作。因为只有9轮,AES的初始轮和第一轮可以用一个查找表来实现。

因此,密钥(如果它确实是 AES 实现,则扩展密钥)被混合到实现中。尽管我不是密码学家,但我猜您可以通过将查找表的值与 Rijndael S-Box 值异或来检索扩展的 AES 密钥。但是,这似乎是一个困难且耗时的过程,因此如果您只想解密一些数据,您可以考虑模拟代码。