欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]TX2017游戏安全知识竞赛第二轮第二题

[原创]TX2017游戏安全知识竞赛第二轮第二题

TX2017游戏安全知识竞赛第二轮第二题

 

一、整体轮廓

IDA载入,发现了TLSCreateThread反调试,直接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,故只要找到函数体位置,手工或者自动实现将操作码数值减去0x0f0xD0以上操作码不变),而操作数不变,即可还原。此处可以用IDA识别MSIL,依次手动修改操作码。

还原后,用Reflector载入,可以看到函数已经正确识别。

本人是对照IDA手工修改的。


可能是由于手工修改有差错,导致ConvertUIntToBytesEncryptDataFile两个函数没有反编译,但这两个函数比较简单,不影响分析。

二、主要算法

1ConvertBytesToUInt,将4字节转化为32位无符号整数。

       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;

       }

2ConvertUIntToBytes,将32位无符号整数转化为4字节。

        private static byte[] ConvertUIntToBytes(uint x)

        {

            byte[] dst = new byte[4];

            for (int i = 0; i < 4; i++)

            {

                dst[i] = (byte)(x & 0xff);

                x = x >> 8;

            }

            return dst;

        }

3CombineBytes,将byte[]连接起来。

        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;

        }

4Code,主要的编码函数,主要的逆向对象,对输入进行0x20轮的编码加密。

        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

        }

5Encrypt,对原数据加密,其中调用了上面这些函数。

        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;

        }

6EncryptDataFile,其主要逻辑即为读入文件(areyouok.png)内容,调用Encrypt对文件内容加密,将加密数据存入另一个文件(areyouok_encrypted

三、算法逆向

Code函数中,num4虽然计算过程很复杂,但其实为固定值0x9e3779b9num3初值为0,最后一轮结束后num3 0xc6ef3720。根据该函数的对成性,可以写出Code函数的逆函数InvCode如下。

        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 };

        }

因此对文件解密过程如下。

            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[i] = wData[i + 7];

            }

 

            wFile.Write(wData, 0, rNum - 7);

解密得到文件data.png,打开如下。

通过以上代码对文件data.png加密,发现加密结果和题中给出结果(data.encrypted文件)一致,说明解密函数是正确的。

winhex打开data.png文件,发现图中阴影部分(第二个“sRGBchunk)与png文件格式无关,且最后chunk最后的CRC32结果也不对,为无效chunk,猜测为嵌入的数据。

将该chunk数据提取出,用winhex载入,发现下图红线处恰好为其后数据大小,这和bmp文件格式一致。

将头两字节改为“BM”,然后另存为xy.bmp

打开xy.bmp,得到最终结果。

全文完。

上传的附件 Program.zip

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功