欢迎来到 嗅灵易学

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

[原创]2017腾讯游戏安全技术竞赛 Round 2 第二题详细题解

[原创]2017腾讯游戏安全技术竞赛 Round 2 第二题详细题解

前言

第二题如果说就让人望而却步的话,这道题怕是要让人吐血身亡了。自己写wp感觉也要写得头发掉光,非常慌。不过既然到了最后一题,为了完整性,硬着头皮也得做下来吧,打开程序一开,里面有dll,采用的是mono框架,是不折不扣的.net的程序。我是第一次接触.net,更别说里面的IL code,jit什么的妖魔鬼怪了,反正现学现卖,不过鬼怪见多了也就慢慢习惯它们的样子了,慢慢就适应了,呃。我还是选择的安卓平台的题目,从评分标准来看,这个题目就分为了3小问,所以我把这个题目分为三大部分,一一阐述每个部分。

0x1 多战寻路--寻到一个坑

正如标题所说,三战寻路--寻到一个坑。本来ida里的函数多数都被我标记得差不多了,因为用IDA F9动态调试的缘故,函数名都丢了(这里我曾崩溃),凑合着看吧,请见谅。
先贴一个main函数:

signed int sub_9EC0()

{

  int v0; // r0@1

  char *v1; // r3@1

  int v2; // r7@1

  int v3; // r0@3

  int v4; // r4@3

  int v5; // r8@3

  int v6; // r0@6

  int v7; // r5@6

  int v8; // r6@7

  int v9; // r1@7

  int v10; // r0@7

  signed int result; // r0@8

  char v12; // [sp+8h] [bp-20h]@1

  int v13; // [sp+Ch] [bp-1Ch]@1

  pthread_create(&v12);

  v13 = 3;

  sub_6C130(&unk_2B4E38, 0);

  v0 = sub_A5B4(&unk_2B4E3A);

  v1 = &byte_36F000;

  v2 = v0;

  do

  {

    *v1 = ~(*v1 ^ 0x11);

    ++v1;

  }

  while ( (_UNKNOWN *)v1 != &unk_370200 );

  v3 = sub_B925C(&byte_36F000, 4608, 1, &v13, 0, &unk_2B4E3A);

  v4 = v13;

  v5 = v3;

  if ( !v13

    && v3

    && sub_6F0A4(v3, &unk_2B4E3A)

    && (v6 = sub_86FB0(v5, "encrypt", "Hello"), (v7 = v6) != 0)

    && (v8 = sub_1196BC(v2, v6), sub_10FA38(v8, v9), (v10 = sub_898DC(v7, "SayHello", v4)) != 0) )

  {

    sub_116D20(v10, v8, v4, v4);

    sub_B9988(v5);

    result = v4;

  }

  else

  {

    result = 1;

  }

  return result;

}

Bypass反调试

说一下反调试先,我一共发现两处。init_array段有一处反调是,虽然不知道怎么判断的,反正把exit nop掉就行了。然后在main函数的起始位置起了一个反调试的线程,把启动的内容nop掉就可以了,这样就可以调试了,也就是可以正式开始漫漫长征路。

主程序流程

先分析一下程序的流程,程序开始会释放一个4.5kb的dll,称之为关键dll,然后通过mono框架加载这个dll 以及在外面的mscorlib.dll,调用mono框架中的一系列函数,寻找到指定的类,再找到sayhello函数,最后调用method_runtime_invoke,print出hello 。

初步修复关键dll

先不管那么多,既然它偷偷的加载dll,直接dump出来看看再说。dump出来的dll为非法文件,使用010editor 已经跟踪代码发现几处问题,Patch了5字节,至少成为了一个看起来正常的dll。
问题1.
5字节之第1字节:PE文件的签名,正常的pe文件签名位PE00,而这个文件的签名为PE01,这会导致NT头找不到,无法被正常识别。
原因:通过跟踪代码可以知道,mono框架只检查了PE,没有检查后面的00是否正确。
附上一个pe文件格式
https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx#other_contents_of_the_file

5字节之第2字节:.net程序里面有一项叫做元数据流,附上
http://www.cnblogs.com/ShaYeBlog/archive/2013/07/20/3202191.html
导致dll继续报错的原因是一个#String的堆,未能识别,导致后面一系列失败,将后面的00 patch成 73即可。
原因:找到解析元数据中堆的地方,贴代码如下:

 else if ( !strncmp(v22 + 8, "#Strings", 7u) )

        {

          v8 = *(_DWORD *)(v15 + 52);

          *(_DWORD *)(v15 + 56) = v8 + read32(v22);

          *(_DWORD *)(v15 + 60) = read32(v22 + 4);

          v22 += 17;

        }

        else if ( !strncmp(v22 + 8, "#US", 4u) )

可以看到,"s"字节没有比较,从这里可以看出来这个mono框架被修改过,而且有点猥琐。而在正常的mono框架中,strncmp的是9位,这就是元数据流异常的原因。

5字节之后3字节:
都在解析和装在MetaData的过程中,有3处高位被改了,导致一般的工具报数组越界的错误。
原因:mono框架修改了read函数,在read的时候只取了低位,这样的话不影响它自身对dll的解析。

修复这5字节,大概dll可以算得上“正常”了,至少扔到反编译工具中不出错了。

三寻IL code 真身

第一寻
一开始没想太多,想的就是可能在loadtable的时候把IL code还原了,然后追踪了很多遍,并没有发现真正的IL code,这里被坑了。局面开始显得僵化,突然发现这样的一个文章:
http://www.cnblogs.com/northstarlight/archive/2010/07/04/1771021.html
以为是隐藏IL code,加了壳,于是就开始了漫漫长路第二寻

第二寻
既然是壳,总会解密,继续跟就行了,外面既然没修改IL code的话,那肯定在更底层了。为了方便调试跟踪以及分析,我把源码下载下来了,地址在这:
https://github.com/mono/mono
然后就变成了苦逼的阅读代码,好,突然发现一个函数,mono_jit_compile_method_with_opt,再跟到mono_jit_compile_method_inner,再跟到mini_method_compile,再跟到mono_runtime_invoke,sayhello都已经执行了,我并没有定位到恢复的IL code,又过了不久,开始第三寻。

第三寻
直接定位mono_method_to_ir,这是最底层解析IL opcode的函数了,我在这下断点跟踪,总能搞定吧。这个函数也是够大,20000行,摘出来,一点一点看,一直跟不到修改IL code 的地方,最后发现,这个函数出题方居然动了,居然用自己的一套来解析Opcode,难怪dll中的IL code乱七八糟也能被执行,终于找到原因了,剩下的就是人肉还原IL code了。

人肉还原IL code

先贴一张Opcode 对应表:

符号					代码				        原来   					修改

CEE_RET					ret 					0x2A					0x39

CEE_BRFALSE_S			        brfalse.s 				0x2C					0x3B

CEE_BRTRUE_S			        brtrue.s 				0x2D					0x3C

CEE_BRFALSE 			        brfalse 				0x39 					0x48						

CEE_BRTRUE 				brtrue 					0x3A					0x49

CEE_BEQ															0x3D

CEE_BGE 														0x3E

CEE_BGT 														0x3F

CEE_BLE 														0x40

CEE_BLT 														0x41

CEE_BNE_UN 														0x42

CEE_BGE_UN 														0x43

CEE_BGT_UN 														0x44

CEE_BLE_UN 														0x45

CEE_BLT_UN 														0x46

下面的有点乱,凑合着看

*************

0x4A

0x4B

0x4C

0x4D

0x4E

0x4F

0x50

0x51

0x52

0x53

CEE_LDIND_I1

CEE_LDIND_U1

CEE_LDIND_I2

CEE_LDIND_U2

CEE_LDIND_I4

CEE_LDIND_U4

CEE_LDIND_I8

CEE_LDIND_I

CEE_LDIND_R4

CEE_LDIND_R8

CEE_LDIND_REF

*************

0x60

0x61

0x62

0x63

0x64

0x65

0x66

0xDF

CEE_STIND_REF

CEE_STIND_I1

CEE_STIND_I2

CEE_STIND_I4

CEE_STIND_I8

CEE_STIND_R4

CEE_STIND_R8

CEE_STIND_I

***************

CEE_ADD

CEE_SUB

CEE_DIV

CEE_DIV_UN

CEE_REM

CEE_REM_UN

CEE_AND

CEE_OR

CEE_XOR

CEE_SHL

CEE_SHR

CEE_SHR_UN

0x67

0x68

0x6A

0x6B

0x6C

0x6D

0x6E

0x6F

0x70

0x71

0x72

0x73

***********************

CEE_LDOBJ 0x80

CEE_LDSTR 0x81

CEE_NEWOBJ 0x82

CEE_REFANYVAL 0xC2

CEE_LDTOKEN 0xD0

0x91

0x92

0x93

0x94

0x95

0x96

0x97

0x98

0x99

0x9A

0xD1

0xD2

0xD3

0xE0

CEE_CONV_OVF_I1_UN

CEE_CONV_OVF_I2_UN

CEE_CONV_OVF_I4_UN

CEE_CONV_OVF_I8_UN

CEE_CONV_OVF_U1_UN

CEE_CONV_OVF_U2_UN

CEE_CONV_OVF_U4_UN

CEE_CONV_OVF_U8_UN

CEE_CONV_OVF_I_UN

CEE_CONV_OVF_U_UN

CEE_CONV_U2

CEE_CONV_U1

CEE_CONV_I

CEE_CONV_U

****************

CEE_STELEM_I

CEE_STELEM_I1

CEE_STELEM_I2

CEE_STELEM_I4

CEE_STELEM_I8

CEE_STELEM_R4

CEE_STELEM_R8

CEE_STELEM_REF

CEE_STELEM

0xAA

0xAB

0xAC

0xAD

0xAE

0xAF

0xB0

0xB1

0xB3

***************

还原好之后效果(贴一个最大的函数):

public static byte[] Encrypt(byte[] input)

{

	uint[] array = new uint[]

	{

		1423373290u,

		363609949u,

		512121596u,

		1703126449u

	};

	byte[] array2 = new byte[0];

	int num = input.Length;

	byte[] array3 = new byte[8];

	int num2 = 7 - num % 8;

	array3[0] = (byte)num2;

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

	{

		array3[i + 1] = (byte)(200 + num2 - i);

	}

	for (int j = 0; j < 7 - num2; j++)

	{

		array3[j + num2 + 1] = input[j];

	}

	uint[] array4 = new uint[]

	{

		MyClass.ConvertBytesToUInt(array3, 0),

		MyClass.ConvertBytesToUInt(array3, 4)

	};

	array4[0] = (array4[0] ^ array[0]);

	array4[1] = (array4[1] ^ array[2]);

	array4 = MyClass.Code(array4, array);

	array2 = MyClass.CombineBytes(array2, MyClass.ConvertUIntToBytes(array4[0]));

	array2 = MyClass.CombineBytes(array2, MyClass.ConvertUIntToBytes(array4[1]));

	for (int k = 7 - num2; k < num; k += 8)

	{

		array4[0] ^= MyClass.ConvertBytesToUInt(input, k);

		array4[1] ^= MyClass.ConvertBytesToUInt(input, k + 4);

		array4 = MyClass.Code(array4, array);

		array2 = MyClass.CombineBytes(array2, MyClass.ConvertUIntToBytes(array4[0]));

		array2 = MyClass.CombineBytes(array2, MyClass.ConvertUIntToBytes(array4[1]));

	}

	return array2;

算法是XTEA的变种,链接这里:
https://en.wikipedia.org/wiki/XTEA

说实话,我这方法有点麻烦,你们出题有点猥琐。

0x2 解密函数解密data

直接上代码,wp写累了:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

namespace TENCENT2017

{

    class Program

    {

        static void Main(string[] args)

        {

            EncryptDataFile();

            DecryptDataFile();

            Console.WriteLine("Hello GSLab!");

        }

        public static void EncryptDataFile()

        {

            byte[] array;

            using (FileStream fileStream = new FileStream("areyouok.png", FileMode.Open))

            {

                using (BinaryReader binaryReader = new BinaryReader(fileStream))

                {

                    array = binaryReader.ReadBytes(Convert.ToInt32(fileStream.Length));

                    array = Encrypt(array);

                }

            }

            using (FileStream fileStream2 = new FileStream("areyouok_encrypted", FileMode.Create))

            {

                using (BinaryWriter binaryWriter = new BinaryWriter(fileStream2))

                {

                    binaryWriter.Write(array);

                }

            }

        }

        public static void DecryptDataFile()

        {

            byte[] array;

            using (FileStream fileStream = new FileStream("data.encrypted", FileMode.Open))

            {

                using (BinaryReader binaryReader = new BinaryReader(fileStream))

                {

                    array = binaryReader.ReadBytes(Convert.ToInt32(fileStream.Length));

                    array = Decrypt(array);

                }

            }

            using (FileStream fileStream2 = new FileStream("de-data.encrypted.png", FileMode.Create))

            {

                using (BinaryWriter binaryWriter = new BinaryWriter(fileStream2))

                {

                    binaryWriter.Write(array);

                }

            }

        }

        public static byte[] Decrypt(byte[] input)

        {

            int num = input.Length;

            uint[] array = new uint[]

	        {

		        1423373290u,

		        363609949u,

		        512121596u,

		        1703126449u

	        };

            byte[] array2 = new byte[0];

            uint[] array4 = new uint[]

	        {

		        0,

		        0

	        };

            uint[] array5 = new uint[]

	        {

		        0,

		        0

	        };

            for (int k = num-4; k >=12; k -= 8)

            {

               array4[1] = ConvertBytesToUInt(input, k );

               array4[0] = ConvertBytesToUInt(input, k-4);

               array4 = DeCode(array4, array);

               array4[1] ^= ConvertBytesToUInt(input, k -8);

               array4[0] ^= ConvertBytesToUInt(input, k - 12);

               array2 = CombineBytes2(array2, ConvertUIntToBytes(array4[1]));

               array2 = CombineBytes2(array2, ConvertUIntToBytes(array4[0]));

               

            }

            return array2;

        }

        private static uint[] DeCode(uint[] v, uint[] k)

        {

            uint num = v[0];

            uint num2 = v[1];

            uint num3 = 0u;

            uint num4 = Convert.ToUInt32(Math.Floor((Math.Pow(5.0, 0.5) - 1.0) * Math.Pow(2.0, 31.0)));

            uint num5 = 32u;

            while (num5-- > 0u)

            {

                num3 += num4;

            }

            num5 = 32u;

            while (num5-- > 0u)

            {

                num2-= (num << 4 ^ (num >> 5) + num ^ num3 + k[(int)((ushort)(num3 >> 11 & 3u))]);

                num3 -= num4;

                num -= (num2 << 4 ^ (num2 >> 5) + num2 ^ num3 + k[(int)((ushort)(num3 & 3u))]);

               Console.WriteLine(" {0} {1} {2}.", num, num2, num3);

                

            }

            return new uint[]

	{

		num,

		num2

	};

        }

        public static byte[] Encrypt(byte[] input)

        {

            uint[] array = new uint[]

	{

		1423373290u,

		363609949u,

		512121596u,

		1703126449u

	};

            byte[] array2 = new byte[0];

            int num = input.Length;

            byte[] array3 = new byte[8];

            int num2 = 7 - num % 8;

            array3[0] = (byte)num2;

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

            {

                array3[i + 1] = (byte)(200 + num2 - i);

            }

            for (int j = 0; j < 7 - num2; j++)

            {

                array3[j + num2 + 1] = input[j];

            }

            uint[] array4 = new uint[]

	{

		ConvertBytesToUInt(array3, 0),

		ConvertBytesToUInt(array3, 4)

	};

            array4[0] = (array4[0] ^ array[0]);

            array4[1] = (array4[1] ^ array[2]);

            array4 = Code(array4, array);

            array2 = CombineBytes(array2, ConvertUIntToBytes(array4[0]));

            array2 = CombineBytes(array2, ConvertUIntToBytes(array4[1]));

            for (int k = 7 - num2; k < num; k += 8)

            {

                array4[0] ^= ConvertBytesToUInt(input, k);

                array4[1] ^= ConvertBytesToUInt(input, k + 4);

                array4 = Code(array4, array);

                array2 = CombineBytes(array2, ConvertUIntToBytes(array4[0]));

                array2 = CombineBytes(array2, ConvertUIntToBytes(array4[1]));

            }

            return array2;

        }

        private static byte[] ConvertUIntToBytes(uint input)

        {

            return new byte[]

	            {

		            (byte)(input & 255u),

		            (byte)(input >> 8 & 255u),

		            (byte)(input >> 16 & 255u),

		            (byte)(input >> 24 & 255u)

	            };

        }

        private static uint ConvertBytesToUInt(byte[] input, int pos)

        {

            uint num = (uint)input[pos];

            num += (uint)((uint)input[pos + 1] << 8);

            num += (uint)((uint)input[pos + 2] << 16);

            return num + (uint)((uint)input[pos + 3] << 24);

        }

        private static byte[] CombineBytes(byte[] bytes1, byte[] bytes2)

        {

            byte[] array = new byte[bytes1.Length + bytes2.Length];

            Buffer.BlockCopy(bytes1, 0, array, 0, bytes1.Length);

            Buffer.BlockCopy(bytes2, 0, array, bytes1.Length, bytes2.Length);

            return array;

        }

        private static byte[] CombineBytes2(byte[] bytes1, byte[] bytes2)

        {

            byte[] array = new byte[bytes1.Length + bytes2.Length];

            Buffer.BlockCopy(bytes2, 0, array, 0, bytes2.Length);

            Buffer.BlockCopy(bytes1, 0, array, bytes2.Length, bytes1.Length);

            return array;

        }

        private static uint[] Code(uint[] v, uint[] k)

        {

            uint num = v[0];

            uint num2 = v[1];

            uint num3 = 0u;

            uint num4 = Convert.ToUInt32(Math.Floor((Math.Pow(5.0, 0.5) - 1.0) * Math.Pow(2.0, 31.0)));

            uint num5 = 32u;

            while (num5-- > 0u)

            {

                num += (num2 << 4 ^ (num2 >> 5) + num2 ^ num3 + k[(int)((ushort)(num3 & 3u))]);

                num3 += num4;

                num2 += (num << 4 ^ (num >> 5) + num ^ num3 + k[(int)((ushort)(num3 >> 11 & 3u))]);

               // Console.WriteLine(" {0} {1} {2}.", num, num2, num3);

            }

            return new uint[]

	{

		num,

		num2

	};

        }

    }

}

0x3 最后的战役

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

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

0 0 0 举报
复制成功