.rsrc
部分提取其有效负载的。我用FindCrypt和IDAScope扫描了此算法,两个插件都未能将其识别为加密算法。
我不熟悉密码算法。
提取代码是由IDA生成的:
int __stdcall sub_9001320(int a1, unsigned int a2, int a3)
{
// a1 = a3 = 0x09003000
// a2 = 0x7600
int result; // eax@2
char v4; // al@23
unsigned int l; // [sp+4h] [bp-4Ch]@33
unsigned int k; // [sp+10h] [bp-40h]@19
unsigned int v7; // [sp+14h] [bp-3Ch]@19
int v8; // [sp+18h] [bp-38h]@40
signed int j; // [sp+1Ch] [bp-34h]@13
unsigned int v10; // [sp+20h] [bp-30h]@5
unsigned int i; // [sp+24h] [bp-2Ch]@5
unsigned int v12; // [sp+28h] [bp-28h]@5
unsigned int v13; // [sp+2Ch] [bp-24h]@5
signed int v14; // [sp+30h] [bp-20h]@13
unsigned int v15; // [sp+34h] [bp-1Ch]@5
unsigned int v16; // [sp+38h] [bp-18h]@5
unsigned __int8 v17; // [sp+3Fh] [bp-11h]@5
signed int v18; // [sp+40h] [bp-10h]@5
_DWORD *lpAddress; // [sp+44h] [bp-Ch]@11
unsigned int v20; // [sp+48h] [bp-8h]@13
unsigned int v21; // [sp+4Ch] [bp-4h]@16
if ( a2 > 0xC )
{
if ( *(_DWORD *)a1 == 1083581807 )
{
v16 = sub_9001300(a1);
v18 = 12;
v17 = 0;
v15 = 9;
v12 = 256;
v13 = 256;
v10 = 8 * a2 - 96;
for ( i = 9; v10 >= i; v10 -= i )
{
if ( i != 12 && v13 == 1 << i )
++i;
++v13;
}
lpAddress = VirtualAlloc(0, 0xC000u, 0x1000u, 4u);
if ( lpAddress )
{
v20 = 0;
v14 = 256;
for ( j = 0; j <= 255; ++j )
{
lpAddress[3 * j] = 0;
lpAddress[3 * j + 2] = 0;
lpAddress[3 * j + 1] = 1;
}
v21 = 0;
while ( v21 < v16 && v12 < v13 )
{
v7 = 0;
for ( k = 0; k < v15; ++k )
{
if ( (1 << (7 - (v17 + 8 * v18) % 8)) & *(_BYTE *)(a1 + (v17 + 8 * v18) / 8) )
v7 |= 1 << k;
v4 = (v17 + 1) % 8;
v17 = (v17 + 1) % 8;
if ( !v4 )
++v18;
}
if ( v7 > 0xFF )
{
if ( v20 < 0xF00 && v7 > v20 + 255 )
return 0;
if ( v21 + lpAddress[3 * v7 + 1] > v16 )
return 0;
for ( l = 0; l < lpAddress[3 * v7 + 1]; ++l )
*(_BYTE *)(a3 + l + v21) = *(_BYTE *)(a3 + l + lpAddress[3 * v7]);
}
else
{
*(_BYTE *)(v21 + a3) = v7;
}
if ( v20 == 3840 )
{
if ( (unsigned int)++v14 >> 12 )
v14 = 256;
}
if ( v20 >= 0xF00 )
{
v8 = v14 - 1;
if ( v14 == 256 )
v8 = 4095;
}
else
{
v8 = v20 + v14;
}
lpAddress[3 * v8] = v21;
lpAddress[3 * v8 + 1] = lpAddress[3 * v7 + 1] + 1;
lpAddress[3 * v8 + 2] = 0;
if ( v20 < 0xF00 )
++v20;
v21 = v21 + lpAddress[3 * v8 + 1] - 1;
++v12;
if ( v15 < 0xC && 1 << v15 == v12 )
++v15;
}
if ( v21 >= v16 )
{
// sub_9001000 is only called onece and always return 0
if ( *(_DWORD *)(a1 + 8) == sub_9001000(a3, v16) )
{
VirtualFree(lpAddress, 0, 0x8000u);
result = 0;
}
else
{
result = 0;
}
}
else
{
result = 0;
}
}
else
{
result = 0;
}
}
else
{
result = 0;
}
}
else
{
result = 0;
}
return result;
}
int __stdcall sub_9001300(int a1)
{
int result; // eax@2
if ( *(_DWORD *)a1 == 1083581807 )
result = *(_DWORD *)(a1 + 4);
else
result = 0;
return result;
}
/>
文件信息:
文件格式pei-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00001000 09001000 09001000 00001000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rdata 00001000 09002000 09002000 00002000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .rsrc 00005000 09003000 09003000 00003000 2**2
CONTENTS, ALLOC, LOAD, DATA
3 .reloc 00001000 09008000 09008000 00008000 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
#1 楼
TL; DR:我们这里所拥有的可能不是加密算法,从外观上看,它更可能是解压缩循环。它根本不做任何与加密类似的事情,甚至在远程也是如此。加密算法分为两类。首先是流密码。来自维基百科:
流密码是一种对称密钥密码,其中明文数字与伪随机密码数字流(密钥流)组合在一起。在流密码中,每个明文数字每次都用密钥流的相应数字加密一次,以给出密文流的数字。由于每个数字的加密取决于密码的当前状态,因此也称为状态密码。实际上,一个数字通常是一个位,而组合操作则是异或(XOR)。
第二个是块密码。来自维基百科:
在密码学中,分组密码是一种确定性算法,可对固定长度的位组(称为块)进行操作,并具有由对称密钥指定的不变变换。分组密码是许多密码协议设计中的重要基本组成部分,被广泛用于实现批量数据的加密。
分组密码的现代设计基于迭代产品密码的概念。克劳德·香农(Claude Shannon)在1949年的重要著作《保密系统通信理论》中分析了产品密码,并建议将它们作为一种有效的安全性手段,通过结合诸如替换和置换之类的简单操作来实现。1迭代产品密码在多个回合中执行加密。它使用从原始密钥派生的另一个子密钥。这种密码的一种广泛实现,以Horst Feistel的名字命名为Feistel网络,尤其是在DES密码中实现。2分组密码的许多其他实现,例如AES,被归类为置换置换网络。
我们这里可能不是加密算法。它根本不做任何与加密类似的远程操作。一些在加密算法中经常发现但在这里不见的东西(不是完整的列表,只是我想出的前几件事):
没有基于加密算法生成的伪随机流内部状态和种子,如流密码中那样
使用XOR(或任何其他操作),不会将生成的流与输入缓冲区的顺序字符组合。
无块结构-在每个漂亮的分块部分上多次执行序列输入消息的内容。
没有冗长而复杂的置换(也没有置换表)。
没有针对分组密码通用的置换码的多次迭代。
没有密钥序列初始化(在两个流中都使用)和分组密码)。
对输入消息或流/状态没有足够的替代。
加密算法通常很长(有人说很难看)。通常对长重复序列中的所有字节执行操作时结构严谨。您经常会看到执行一个或几个操作的固定长度的多次迭代。
这更像是一种压缩/解压缩算法,因为字节序列在某些条件下被复制,而字节被构造到解压缩的缓冲区中
让我们遍历代码,然后查看:
int __stdcall sub_9001320(int a1, unsigned int a2, int a3)
{
// a1 = a3 = 0x09003000
// a2 = 0x7600
int result; // eax@2
char v4; // al@23
unsigned int l; // [sp+4h] [bp-4Ch]@33
unsigned int k; // [sp+10h] [bp-40h]@19
unsigned int v7; // [sp+14h] [bp-3Ch]@19
int v8; // [sp+18h] [bp-38h]@40
signed int j; // [sp+1Ch] [bp-34h]@13
unsigned int v10; // [sp+20h] [bp-30h]@5
unsigned int i; // [sp+24h] [bp-2Ch]@5
unsigned int v12; // [sp+28h] [bp-28h]@5
unsigned int v13; // [sp+2Ch] [bp-24h]@5
signed int v14; // [sp+30h] [bp-20h]@13
unsigned int v15; // [sp+34h] [bp-1Ch]@5
unsigned int v16; // [sp+38h] [bp-18h]@5
unsigned __int8 v17; // [sp+3Fh] [bp-11h]@5
signed int v18; // [sp+40h] [bp-10h]@5
_DWORD *lpAddress; // [sp+44h] [bp-Ch]@11
unsigned int v20; // [sp+48h] [bp-8h]@13
unsigned int v21; // [sp+4Ch] [bp-4h]@16
函数定义
if ( a2 > 0xC )
{
确定一定的长度至少为96个字节。尽管有一些块密码接受此块大小,但它并不是太普遍,并且最广泛接受的块密码不支持此块大小。
if ( *(_DWORD *)a1 == 1083581807 )
{
确定缓冲区的某些第一个字节是固定的,这看起来
v16 = sub_9001300(a1);
将
v16
设置为输入缓冲区的第二个双字 v18 = 12;
v17 = 0;
v15 = 9;
v12 = 256;
v13 = 256;
v10 = 8 * a2 - 96;
更多变量初始化
for ( i = 9; v10 >= i; v10 -= i )
{
if ( i != 12 && v13 == 1 << i )
++i;
++v13;
}
根据原始长度增加
v13
。在这里无需过多说明,这看起来不像任何密码种子/初始化。 lpAddress = VirtualAlloc(0, 0xC000u, 0x1000u, 4u);
if ( lpAddress )
{
分配硬编码的长度缓冲区以用作临时数据
v20 = 0;
v14 = 256;
for ( j = 0; j <= 255; ++j )
{
lpAddress[3 * j] = 0;
lpAddress[3 * j + 2] = 0;
lpAddress[3 * j + 1] = 1;
}
初始化表示缓冲区,2/3为0,1 / 3为1s。这可能是布尔缓冲区。
循环
v16
次。请记住,v16
是用户提供的,它是提供的缓冲区中的第二个双字。魔术之后的第二个双字可能是长度。 v21 = 0;
while ( v21 < v16 && v12 < v13 )
{
另一个循环,这一次是9次迭代。这意味着我们在猜测的length参数中为每个字符进行了9次迭代。大多数分组密码将具有更多的迭代,而这些迭代不会按字符进行。大多数流密码不会处理数据,而是生成要与XOR进行比较的字节流。我们也没有在这里看到异或。
v7 = 0;
for ( k = 0; k < v15; ++k )
{
位仅被添加到
v7
,对于任何类型的加密算法也不起作用。 if ( (1 << (7 - (v17 + 8 * v18) % 8)) & *(_BYTE *)(a1 + (v17 + 8 * v18) / 8) )
v7 |= 1 << k;
这两个都是字节-大小的计数器。
v4 = (v17 + 1) % 8;
v17 = (v17 + 1) % 8;
增加另一个计数器,用于确定在
v7
中将设置多少位。整个循环决定v7
的哪些位,取决于固定的顺序,并且输入缓冲区的某些位是否为 if ( !v4 )
++v18;
}
如果
v7
大于0xff,请进行一些检查(并返回无效状态/数据),然后复制一个字节序列,直到从a3到a3到达空终止符为止,偏移量由lpAddress
的值确定,位于v7
确定的特定偏移量处。 if ( v7 > 0xFF )
{
if ( v20 < 0xF00 && v7 > v20 + 255 )
return 0;
if ( v21 + lpAddress[3 * v7 + 1] > v16 )
return 0;
for ( l = 0; l < lpAddress[3 * v7 + 1]; ++l )
*(_BYTE *)(a3 + l + v21) = *(_BYTE *)(a3 + l + lpAddress[3 * v7]);
}
如果
v7
小于或等于等于255,只需将其分配到指定位置即可。根据偏移变量
v8
及其自身的值重置v4
和v20
的值。 else
{
*(_BYTE *)(v21 + a3) = v7;
}
设置一些基于
lpAddress
的硬编码值(0)中的其他值和单个值的副本。 if ( v20 == 3840 )
{
if ( (unsigned int)++v14 >> 12 )
v14 = 256;
}
if ( v20 >= 0xF00 )
{
v8 = v14 - 1;
if ( v14 == 256 )
v8 = 4095;
}
else
{
v8 = v20 + v14;
}
基于输入的更多计数器更新和溢出复位。
lpAddress[3 * v8] = v21;
lpAddress[3 * v8 + 1] = lpAddress[3 * v7 + 1] + 1;
lpAddress[3 * v8 + 2] = 0;
如果输入无效,则返回0
if ( v20 < 0xF00 )
++v20;
v21 = v21 + lpAddress[3 * v8 + 1] - 1;
++v12;
if ( v15 < 0xC && 1 << v15 == v12 )
++v15;
}
声明缓冲区以硬编码双字开头(我们已经知道是由于前面的if语句导致的情况),然后在第二个dword中返回值。
评论
甚至没有一点P-box或S-box,更不用说旋转指令序列了。
–约翰·格林
19/12/2在22:10
评论
它似乎是一种压缩算法,而不是加密算法。