我认为您正确地确定了函数的用途及其参数。因此,该函数sub_44d84
有四个参数:(<decryptionFunction=0x1>, <IV>, <bytesToDecrypt>, <decryptedBytes>)
并sub_46d554
使用 CBC 为每个块调用该函数。
该sub_46d554
函数接收2个参数:
输入块中的字节从函数的第一部分读出,执行解密步骤并写入函数最后一部分的输出缓冲区。
要了解解密功能,首先应该了解混淆的构建块。所以,我在从输入块中读出第二个字节后拿起了一些示例代码(由于编译器优化,函数的开头有点乱)。
在上图中,标有绿色的线读出输入块的第二个字节,橙色线初始化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 密钥。但是,这似乎是一个困难且耗时的过程,因此如果您只想解密一些数据,您可以考虑模拟代码。