首页
论坛
课程
招聘
[原创]TX2017游戏安全知识竞赛第二轮第二题
2017-7-28 10:50 4325

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

loudy 活跃值
10
2017-7-28 10:50
4325

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,得到最终结果。

全文完。


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

上传的附件:
收藏
点赞0
打赏
分享
打赏 + 2.00
打赏次数 1 金额 + 2.00
 
赞赏  orz1ruo   +2.00 2017/07/28
最新回复 (2)
雪    币: 82
活跃值: 活跃值 (16)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
bugshunter 活跃值 2021-1-30 11:02
2
0
感谢分享,虽然看的不太明白
雪    币: 1192
活跃值: 活跃值 (285)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dsqyg 活跃值 2021-3-8 11:15
3
0
GG有意思, 哈哈哈
游客
登录 | 注册 方可回帖
返回