本帖最后由 loudy 于 2017-10-17 08:37 编辑
最近新开了一个微信公众号:风云处(fyc1687),欢迎大家多多交流。其中我会逐步把我学习逆向的经验、实例、技巧分享出来。
一、整体轮廓
IDA载入,发现了TLS和CreateThread反调试,直接nop掉即可。程序流程很清晰,但其中调用了mono生成的dll,且程序中自己实现了donet虚拟机(简单修改了msil指令集),导致dump出的encrypt.dll无法分析,也无法找出其中的函数。
二、encrypt.dll处理
找到关键位置,发现明显的PE文件特征,dump大小为0x1200。
该dll文件进行了人为修改,导致PE编辑工具无法识别。结合PE格式手工识别,容易发现下图红线处应该为0。
修改后PE编辑工具可识别,但其中的donet元数据识别不准确,元数据信息也被修改。
对照donet元数据格式,修改文件中下图标红出(自己生成一个donet的Dll文件,对照看更佳,本人就是用这种办法),第二条红线处改为‘s’,另外三条红线处清0(具体请参考doNet格式)。
对照修改,直到能识别所有元数据,修改完成后如下图所示。
此时可以用Reflector载入,但Refector只能识别类结构和函数名,却无法准确识别函数结构体,函数的实现看不到。实际上程序中用mono实现了一个本地.net运行时,经过分析发现,程序中实现的doNet虚拟机指令即在标准指令操作码的基础上加了0x0f,故只要找到函数体位置,手工或者自动实现将操作码数值减去0x0f(0xD0以上操作码不变),而操作数不变,即可还原。此处可以用IDA识别MSIL,依次手动修改操作码。还原后,用Reflector载入,可以看到函数已经正确识别。
本人是对照IDA手工修改的。
可能是由于手工修改有差错,导致ConvertUIntToBytes和EncryptDataFile两个函数没有反编译,但这两个函数比较简单,不影响分析。
二、主要算法
(1)ConvertBytesToUInt,将4字节转化为32位无符号整数。
[C#] 纯文本查看 复制代码 private static uint ConvertBytesToUInt(byte[] input, int pos)
{
// This item is obfuscated and can not be translated.
uint num = (uint)(input[pos]) + (uint)(input[pos + 1] << 0x8) + (uint)(input[pos + 2] << 0x10) + (uint)(input[pos + 3] << 0x18);
return num;
}
(2)ConvertUIntToBytes,将32位无符号整数转化为4字节。
[C#] 纯文本查看 复制代码 private static byte[] ConvertUIntToBytes(uint x)
{
byte[] dst = new byte[4];
for (int i = 0; i < 4; i++)
{
dst = (byte)(x & 0xff);
x = x >> 8;
}
return dst;
}
(3)CombineBytes,将byte[]连接起来。
[C#] 纯文本查看 复制代码 private static byte[] CombineBytes(byte[] bytes1, byte[] bytes2)
{
byte[] dst = new byte[bytes1.Length + bytes2.Length];
Buffer.BlockCopy(bytes1, 0, dst, 0, bytes1.Length);
Buffer.BlockCopy(bytes2, 0, dst, bytes1.Length, bytes2.Length);
return dst;
}
(4)Code,主要的编码函数,主要的逆向对象,对输入进行0x20轮的编码加密。
[C#] 纯文本查看 复制代码 private static uint[] Code(uint[] v, uint[] k)
{
uint num = v[0];//0x54d6f3ea
uint num2 = v[1];//0x1e865afc
uint num3 = 0;
uint num4 = Convert.ToUInt32(Math.Floor((double)((Math.Pow(5.0, 0.5) - 1.0) * Math.Pow(2.0, 31.0))));
uint num5 = 0x20;
while (num5-- > 0)
{
num += ((num2 << 4) ^ ((num2 >> 5) + num2)) ^ (num3 + k[(ushort)(num3 & 3)]);
num3 += num4;
num2 += ((num << 4) ^ ((num >> 5) + num)) ^ (num3 + k[(ushort)((num3 >> 11) & 3)]);
}
return new uint[] { num, num2 }; //0xbfd3b335 0xcc918c5e
}
(5)Encrypt,对原数据加密,其中调用了上面这些函数。
[C#] 纯文本查看 复制代码 public static byte[] Encrypt(byte[] input)
{
uint[] k = new uint[] { 0x54d6f3ea, 0x15ac3f5d, 0x1e865afc, 0x6583a5b1 };
byte[] buffer = new byte[0];
int length = input.Length;
byte[] buffer2 = new byte[8];
int num2 = 7 - (length % 8);
buffer2[0] = (byte)num2;
for (int i = 0; i < num2; i++)
{
buffer2[i + 1] = (byte)((200 + num2) - i);
}
for (int j = 0; j < (7 - num2); j++)
{
buffer2[(j + num2) + 1] = input[j];
}
uint[] v = new uint[] { ConvertBytesToUInt(buffer2, 0), ConvertBytesToUInt(buffer2, 4) };
v[0] ^= k[0];
v[1] ^= k[2];
v = Code(v, k);
buffer = CombineBytes(CombineBytes(buffer, ConvertUIntToBytes(v[0])), ConvertUIntToBytes(v[1]));
for (int m = 7 - num2; m < length; m += 8)
{
v[0] ^= ConvertBytesToUInt(input, m);
v[1] ^= ConvertBytesToUInt(input, m + 4);
v = Code(v, k);
buffer = CombineBytes(CombineBytes(buffer, ConvertUIntToBytes(v[0])), ConvertUIntToBytes(v[1]));
}
return buffer;
}
(6)EncryptDataFile,其主要逻辑即为读入文件(areyouok.png)内容,调用Encrypt对文件内容加密,将加密数据存入另一个文件(areyouok_encrypted)。
三、算法逆向
Code函数中,num4虽然计算过程很复杂,但其实为固定值0x9e3779b9;num3初值为0,最后一轮结束后num3 为 0xc6ef3720。根据该函数的对成性,可以写出Code函数的逆函数InvCode如下。
[C#] 纯文本查看 复制代码 private static uint[] InvCode(uint[] v, uint[] k)
{
uint num = v[0];
uint num2 = v[1];
uint num3 = 0xc6ef3720;
uint num4 = Convert.ToUInt32(Math.Floor((double)((Math.Pow(5.0, 0.5) - 1.0) * Math.Pow(2.0, 31.0))));
uint num5 = 0x20;
while (num5-- > 0)
{
num2 -= ((num << 4) ^ ((num >> 5) + num)) ^ (num3 + k[(ushort)((num3 >> 11) & 3)]);
num3 -= num4;
num -= ((num2 << 4) ^ ((num2 >> 5) + num2)) ^ (num3 + k[(ushort)(num3 & 3)]);
}
return new uint[] { num, num2 };
}
因此对文件解密过程如下。
[C#] 纯文本查看 复制代码 int rNum = 0x1be8;
byte[] rData = new byte[rNum];
byte[] wData = new byte[0];
FileStream rFile = new FileStream("data.encrypted", FileMode.Open);
FileStream wFile = new FileStream("data.png",FileMode.Create);
rFile.Read(rData, 0, rNum);
uint x0 = 0, x1 = 0, x00 = 0, x11 = 0;
uint[] k = new uint[] { 0x54d6f3ea, 0x15ac3f5d, 0x1e865afc, 0x6583a5b1 };
for (int i = 0; i < rNum; i = i + 8)
{
uint[] v = new uint[] { ConvertBytesToUInt(rData, i), ConvertBytesToUInt(rData, i+4) };
x00 = v[0];
x11 = v[1];
v = InvCode(v, k);
if (i == 0)
{
v[0] ^= k[0];
v[1] ^= k[2];
}
v[0] ^= x0;
v[1] ^= x1;
x0 = x00;
x1 = x11;
wData = CombineBytes(CombineBytes(wData, ConvertUIntToBytes(v[0])), ConvertUIntToBytes(v[1]));
}
for (int i = 0; i < rNum - 7; i++)
{
wData = wData[i + 7];
}
wFile.Write(wData, 0, rNum - 7);
解密得到文件data.png,打开如下。
通过以上代码对文件data.png加密,发现加密结果和题中给出结果(data.encrypted文件)一致,说明解密函数是正确的。用winhex打开data.png文件,发现图中阴影部分(第二个“sRGB”chunk)与png文件格式无关,且最后chunk最后的CRC32结果也不对,为无效chunk,猜测为嵌入的数据。
将该chunk数据提取出,用winhex载入,发现下图红线处恰好为其后数据大小,这和bmp文件格式一致。
将头两字节改为“BM”,然后另存为xy.bmp。
打开xy.bmp,得到最终结果。
全文完。 |