破解某游戏修改器时的意外收获
2024-1-22 16:3:33 Author: 小艾搞安全(查看原文) 阅读量:251 收藏

原文链接:http://hexo.iyzyi.com/2023/06/11/%E7%A0%B4%E8%A7%A3%E6%9F%90%E6%B8%B8%E6%88%8F%E4%BF%AE%E6%94%B9%E5%99%A8%E6%97%B6%E7%9A%84%E6%84%8F%E5%A4%96%E6%94%B6%E8%8E%B7/

本来是想简单破解一下某国产单机游戏的修改器,没想到还有意外收获。


今天没事干,翻出来一款国产单机游戏,重复刷刷刷太无聊了,于是网上下了个修改器,没想到需要购买激活码才能用。并不是很贵,只要十块几毛钱,在发卡的网站上直接购买即可,推测是支付完就可以获得一个激活码,不需要人工介入。虽然不贵,但是作为一个逆向选手,难得遇到一个锻炼自己的机会,直接开搞喽。

简单一看,是个c#写的修改器,拖进dnspy里面,感觉像是被混淆了:

Decrypt是这样的:

private static GCHandle Decrypt(uint[] A_0, uint A_1)
{
 uint[] array = new uint[16];
 uint[] array2 = new uint[16];
 ulong num = (ulong)A_1;
 for (int i = 0; i < 16; i++)
 {
  num = num * num % 339722377UL;
  array2[i] = (uint)num;
  array[i] = (uint)(num * num % 1145919227UL);
 }
 array[0] = (array[0] ^ array2[0]) * 2849015165u;
 array[1] = (array[1] ^ array2[1] ^ 2786556537u);
 array[2] = array[2] * array2[2] * 2849015165u;
 array[3] = array[3] * array2[3] + 1807995571u;
 array[4] = (array[4] ^ array2[4]) + 1807995571u;
 array[5] = (array[5] ^ array2[5]) + 1807995571u;
 array[6] = (array[6] * array2[6] ^ 2786556537u);
 array[7] = (array[7] + array2[7]) * 2849015165u;
 array[8] = array[8] * array2[8] * 2849015165u;
 array[9] = (array[9] + array2[9] ^ 2786556537u);
 array[10] = array[10] + array2[10] + 1807995571u;
 array[11] = array[11] * array2[11] * 2849015165u;
 array[12] = (array[12] ^ array2[12]) * 2849015165u;
 array[13] = (array[13] + array2[13] ^ 2786556537u);
 array[14] = (array[14] ^ array2[14] ^ 2786556537u);
 array[15] = (array[15] * array2[15] ^ 2786556537u);
 Array.Clear(array2, 0, 16);
 byte[] array3 = new byte[A_0.Length << 2];
 uint num2 = 0u;
 for (int j = 0; j < A_0.Length; j++)
 {
  uint num3 = A_0[j] ^ array[j & 15];
  array[j & 15] = (array[j & 15] ^ num3) + 1037772825u;
  array3[(int)((UIntPtr)num2)] = (byte)num3;
  array3[(int)((UIntPtr)(num2 + 1u))] = (byte)(num3 >> 8);
  array3[(int)((UIntPtr)(num2 + 2u))] = (byte)(num3 >> 16);
  array3[(int)((UIntPtr)(num2 + 3u))] = (byte)(num3 >> 24);
  num2 += 4u;
 }
 Array.Clear(array, 0, 16);
 byte[] array4 = <Module>.Decompress(array3);
 Array.Clear(array3, 0, array3.Length);
 GCHandle result = GCHandle.Alloc(array4, GCHandleType.Pinned);
 ulong num4 = num % 9067703UL;
 for (int k = 0; k < array4.Length; k++)
 {
  byte[] array5 = array4;
  int num5 = k;
  array5[num5] ^= (byte)num;
  if ((k & 255) == 0)
  {
   num = num * num % 9067703UL;
  }
 }
 return result;
}

Decompress是这样的:

// <Module>
// Token: 0x06000004 RID: 4 RVA: 0x00016364 File Offset: 0x00014564
internal static byte[] Decompress(byte[] A_0)
{
 MemoryStream memoryStream = new MemoryStream(A_0);
 <Module>.LzmaDecoder lzmaDecoder = new <Module>.LzmaDecoder();
 byte[] array = new byte[5];
 memoryStream.Read(array, 0, 5);
 lzmaDecoder.SetDecoderProperties(array);
 long num = 0L;
 for (int i = 0; i < 8; i++)
 {
  int num2 = memoryStream.ReadByte();
  num |= (long)((long)((ulong)((byte)num2)) << 8 * i);
 }
 byte[] array2 = new byte[(int)num];
 MemoryStream memoryStream2 = new MemoryStream(array2, true);
 long num3 = memoryStream.Length - 13L;
 lzmaDecoder.Code(memoryStream, memoryStream2, num3, num);
 return array2;
}

<Module>.Main是这样的:

private static int Main(string[] A_0)
{
 uint[] array = new uint[]
 {
  2433042623u,
  3134412526u,
  156519932u,
  2628545700u,
  // 此后省略,共20244个uint
 };
 Assembly executingAssembly = Assembly.GetExecutingAssembly();
 Module manifestModule = executingAssembly.ManifestModule;
 GCHandle gchandle = <Module>.Decrypt(array, 4159999126u);
 byte[] array2 = (byte[])gchandle.Target;
 Module module = executingAssembly.LoadModule("koi", array2);
 Array.Clear(array2, 0, array2.Length);
 gchandle.Free();
 Array.Clear(array, 0, array.Length);
 <Module>.key = manifestModule.ResolveSignature(285212673);
 AppDomain.CurrentDomain.AssemblyResolve += <Module>.Resolve;
 module.GetTypes();
 MethodBase methodBase = module.ResolveMethod((int)<Module>.key[0] | (int)<Module>.key[1] << 8 | (int)<Module>.key[2] << 16 | (int)<Module>.key[3] << 24);
 object[] array3 = new object[methodBase.GetParameters().Length];
 if (array3.Length != 0)
 {
  array3[0] = A_0;
 }
 object obj = methodBase.Invoke(null, array3);
 if (obj is int)
 {
  return (int)obj;
 }
 return 0;
}

Main中的array里面有两万多个uint数据,肯定是混淆无疑了。

13行的<Module>.Decrypt(array, 4159999126u)解密得到gchandle:

15行把gchandle.Target加载成module,其ScopeName为koi:

然后再通过后续几行代码的数据变化,最后于28行,通过反射,调用处理后的数据所形成的函数/方法:

不到万不得已,我真的不想手动处理混淆。先换条路试试看。

掏出fiddler抓包,没抓到,所以激活码验证时大概率使用的不是http(s)协议。

进而掏出wireshark抓包,这次抓到了。随即大吃一惊,居然是tds协议:

tds协议是微软sql server通信的协议。这个修改器居然在本地客户端使用这个协议,那数据库的用户名和密码也必然存在于本地客户端中。

这里我简单介绍一下上图的各个数据包。

第一个数据包是客户端发往数据库的服务端,传输一些预登录的信息,比如版本号等:

第二个数据包是服务端针对第一个数据包的响应。

后面的四个数据包是tls包,进行数据库登录操作。注意,这个tls包的架构是ip->tcp->tds->tls,tls的数据是作为tds的负载而存在的、tds的数据是作为tcp的负载而存在的;而非我们常见的ip->tcp->tls:

然后通过一个tls数据包加密传输数据库的账号和密码,注意这个数据包是真正的ip->tcp->tls的数据包,不是tds数据包,所以过滤条件是tds的话看不到这个包,需要追踪tcp流才能看到:

另外,我在检索资料时发现,有的sql server数据库登录其实没有使用tls,就直接是账号+密码,比如:

(sa是sql server的管理员用户名)

我们回到正题。随后的那个Response是一些登录成功后的相关信息,是tds数据包,明文形式的:

最后四个数据包,两两组合,分别是数据库查询和对应的查询结果:

分析到这里,我们有了意外收获:数据库的账号和密码都存在于这个修改器的本地客户端中。

它可能会被混淆了,被加壳了,或是其他怎么样了让我们难以找到,但必然存在某一时刻,它是存在于程序中的,不然是不可能通过tds协议连接上远程的microsoft sql server的。

走到这里,我们的目标很明确,不必去绞尽脑汁地在本地程序中绕过激活码,只需要拿到数据库的账号和密码,远程连接上数据库,拿到数据库里的激活码即可,这简直比我们预期想要的成果高到不知道哪里去了。

所以,怎么拿到数据库的账号和密码呢?两个思考的角度:流量中;程序中。

流量中

虽然账号和密码是通过变种tls(指ip->tcp->tds->tls)加密传输的,但是有没有解密的方法呢?

我们先来看下,正常的tls的加密流量如何解密呢?有且只有两种方案:

  1. 服务端也是我们的,我们有服务端的私钥,因此可以解密加密流量(但这是几乎不可能的)

  2. 在tls客户端中”插桩”,导出预主密钥(比如chrome和firefox都会将相关信息写入环境变量 SSLKEYLOGFILE的值对应的文件中)

具体可参考:

解密TLS协议全记录之利用wireshark解密 (PS:单纯依靠wireshark中的信息是不可能解密tls的,必须从外部引入额外的信息才有可能)

HTTPS 篇之 SSL 握手过程详解 | 渡渡岛

密钥交换算法 - 廖雪峰的官方网站

但是经过尝试,c#中的tls实现(更具体一点是基于tds的tls),并没有像chrome那些会把tls密钥相关的信息记录到相应的文件中。此路不通。

程序中

没办法,只能处理一下混淆了,关键就是要恢复处理后的那些数据。

随手复制了一行代码,检索了一下,居然是现有的混淆方案,名字是ConfuserEx。我去,早知道我还费那么大劲抓包干嘛。。。

这里放一篇检索到的使用了该混淆方案的病毒样本的分析报告:Sophisticated Mutli-stage Malware (hosted on pussyhunters.ru) – Malware Analysis

顺藤摸瓜很容易就找到了一个对应的脱壳脚本:hackovh/ConfuserEx-Unpacker-2 (github.com),同样也是c#写的,编译后直接脚本.exe 目标.exe,然后等一会就处理好了。可能是脚本比较老,比较是五年前的了,所以脱壳后的程序不能运行,只能静态分析。

脱壳后大概是这样的:

其中在HT.Activate中,我们发现了数据库的密码。。。

using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Management;

namespace HT
{
 // Token: 0x02000004 RID: 4
 public class Activate
 {
  // Token: 0x0600000D RID: 13 RVA: 0x000020CF File Offset: 0x000002CF
  internal static bool GetActivateState()
  {
   return Activate.activateState;
  }

  // Token: 0x0600000E RID: 14 RVA: 0x0000264C File Offset: 0x0000084C
  internal static bool RunActivate(string path, string key)
  {
   bool result;
   try
   {
    string cpuInfo = Activate.GetCpuInfo();
    string cmdText = string.Concat(new string[]
    {
     "UPDATE GuiGuBaHuangKey SET Code='",
     cpuInfo,
     "' WHERE [Key]='",
     key,
     "' AND (Code IS NULL OR Code='",
     cpuInfo,
     "')"
    });
    using (SqlConnection sqlConnection = new SqlConnection(Activate.connstr))
    {
     if (sqlConnection.State != ConnectionState.Open)
     {
      sqlConnection.Open();
     }
     using (SqlCommand sqlCommand = new SqlCommand(cmdText, sqlConnection))
     {
      if (sqlCommand.ExecuteNonQuery() > 0)
      {
       Activate.activateState = true;
       Activate.CreateKeyFile(path, key);
       result = true;
      }
      else
      {
       Activate.activateState = false;
       result = false;
      }
     }
    }
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x0600000F RID: 15 RVA: 0x00002720 File Offset: 0x00000920
  internal static bool IsActivate(string path)
  {
   bool result;
   try
   {
    string key = Activate.GetKey(path);
    string cpuInfo = Activate.GetCpuInfo();
    string cmdText = "SELECT Code FROM GuiGuBaHuangKey WHERE [Key] = '" + key + "'";
    using (SqlConnection sqlConnection = new SqlConnection(Activate.connstr))
    {
     using (SqlCommand sqlCommand = new SqlCommand(cmdText, sqlConnection))
     {
      if (sqlConnection.State != ConnectionState.Open)
      {
       sqlConnection.Open();
      }
      using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
      {
       if (sqlDataReader.Read() && string.Equals(cpuInfo, sqlDataReader.GetString(0)))
       {
        Activate.activateState = true;
        result = true;
       }
       else
       {
        Activate.activateState = false;
        result = false;
       }
      }
     }
    }
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x06000010 RID: 16 RVA: 0x00002804 File Offset: 0x00000A04
  internal static string GetClientVer()
  {
   string result;
   try
   {
    string cmdText = "SELECT ClientVer FROM GuiGuBaHuangVer WHERE ID = '1'";
    using (SqlConnection sqlConnection = new SqlConnection(Activate.connstr))
    {
     using (SqlCommand sqlCommand = new SqlCommand(cmdText, sqlConnection))
     {
      if (sqlConnection.State != ConnectionState.Open)
      {
       sqlConnection.Open();
      }
      using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
      {
       if (sqlDataReader.Read())
       {
        result = sqlDataReader.GetString(0);
       }
       else
       {
        result = string.Empty;
       }
      }
     }
    }
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x06000011 RID: 17 RVA: 0x000028B4 File Offset: 0x00000AB4
  internal static string GetServerVer()
  {
   string result;
   try
   {
    string cmdText = "SELECT ServerVer FROM GuiGuBaHuangVer WHERE ID = '1'";
    using (SqlConnection sqlConnection = new SqlConnection(Activate.connstr))
    {
     using (SqlCommand sqlCommand = new SqlCommand(cmdText, sqlConnection))
     {
      if (sqlConnection.State != ConnectionState.Open)
      {
       sqlConnection.Open();
      }
      using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
      {
       if (sqlDataReader.Read())
       {
        result = sqlDataReader.GetString(0);
       }
       else
       {
        result = string.Empty;
       }
      }
     }
    }
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x06000012 RID: 18 RVA: 0x00002964 File Offset: 0x00000B64
  internal static string GetUrl()
  {
   string result;
   try
   {
    string cmdText = "SELECT Url FROM GuiGuBaHuangUrl WHERE ID = '1'";
    using (SqlConnection sqlConnection = new SqlConnection(Activate.connstr))
    {
     using (SqlCommand sqlCommand = new SqlCommand(cmdText, sqlConnection))
     {
      if (sqlConnection.State != ConnectionState.Open)
      {
       sqlConnection.Open();
      }
      using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
      {
       if (sqlDataReader.Read())
       {
        result = sqlDataReader.GetString(0);
       }
       else
       {
        result = string.Empty;
       }
      }
     }
    }
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x06000013 RID: 19 RVA: 0x00002A14 File Offset: 0x00000C14
  internal static string GetUpdateUrl()
  {
   string result;
   try
   {
    string cmdText = "SELECT UpdateUrl FROM GuiGuBaHuangUrl WHERE ID = '1'";
    using (SqlConnection sqlConnection = new SqlConnection(Activate.connstr))
    {
     using (SqlCommand sqlCommand = new SqlCommand(cmdText, sqlConnection))
     {
      if (sqlConnection.State != ConnectionState.Open)
      {
       sqlConnection.Open();
      }
      using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
      {
       if (sqlDataReader.Read())
       {
        result = sqlDataReader.GetString(0);
       }
       else
       {
        result = string.Empty;
       }
      }
     }
    }
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x06000014 RID: 20 RVA: 0x00002AC4 File Offset: 0x00000CC4
  private static string GetKey(string path)
  {
   string result;
   try
   {
    using (FileStream fileStream = new FileStream(path + Activate.keyName, FileMode.Open, FileAccess.Read))
    {
     using (StreamReader streamReader = new StreamReader(fileStream))
     {
      result = streamReader.ReadLine();
     }
    }
   }
   catch
   {
    result = string.Empty;
   }
   return result;
  }

  // Token: 0x06000015 RID: 21 RVA: 0x00002B3C File Offset: 0x00000D3C
  private static void CreateKeyFile(string path, string key)
  {
   using (FileStream fileStream = new FileStream(path + Activate.keyName, FileMode.Create, FileAccess.ReadWrite))
   {
    using (StreamWriter streamWriter = new StreamWriter(fileStream))
    {
     streamWriter.Write(key);
     streamWriter.Flush();
    }
   }
  }

  // Token: 0x06000016 RID: 22 RVA: 0x00002BA4 File Offset: 0x00000DA4
  private static string GetCpuInfo()
  {
   string result;
   try
   {
    string text = "";
    using (ManagementClass managementClass = new ManagementClass("Win32_Processor"))
    {
     ManagementObjectCollection instances = managementClass.GetInstances();
     using (ManagementObjectCollection.ManagementObjectEnumerator enumerator = instances.GetEnumerator())
     {
      if (enumerator.MoveNext())
      {
       text = ((ManagementObject)enumerator.Current).Properties["ProcessorId"].Value.ToString();
      }
     }
     instances.Dispose();
    }
    result = text.ToString();
   }
   catch
   {
    throw;
   }
   return result;
  }

  // Token: 0x04000003 RID: 3
  private static bool activateState = false;

  // Token: 0x04000004 RID: 4
  private static string keyName = "key.dat";

  // Token: 0x04000005 RID: 5
  private static string connstr = "Data Source=***.my3w.com;Initial Catalog=b***4_db;User ID=b***4;Password=***";
 }
}

有了数据库密码,直接登录:

查询版本:

查询url:

最激动人心的时刻到喽,查询激活码:

Key就是激活码,Code是通过cpu等信息计算出来的。如果Code为Null,说明这个激活码还没使用过,如果不为Null,说明这个激活码已经被使用过了。

我们来验证一下。首先选择要给Code不为Null的激活码,验证失败:

然后选一个Code为Null的,直接激活成功。激活成功后会在目录下写入一个key.dat,内容就是激活码:

尾声

拿下数据库,我们再大胆一点,看下能不能拿下这台远程主机。然而扫描发现ssh和3389都没开:

最后很可惜,通过域名***.my3w.com以及对应ip属地的查询,我们可以看出这个是阿里云北京的一台虚拟主机,是不能远程登录的。

此外,my3w.com是阿里云的域名,***.my3w.com是万网提供的临时域名。

项目下载
号后台回复:3618,获取微信官方3618安装包下载链接。
众号后台回复:cs45,获取catcs4.5下载链接。
众号后台回复:cs49,获取CobaltStrike4.9原版jar破解下载链接。
众号后台回复:权限维持,获取CobaltStrike权限维持插件下载链接。
公众号后台回复:千寻,获取小艾魔改千寻框架下载链接。
公众号后台回复:chat,获取vx_chatgpt的下载链接。
公众号后台回复:jboss,获取jboss利用工具下载链接。
公众号后台回复:ruoyi,获取ruoyi利用工具下载链接。
公众号后台回复:162,获取Liqun16.2 工具包下载链接。
公众号后台回复:burp,获取burp2022.9.1破解版下载链接。

文章来源: http://mp.weixin.qq.com/s?__biz=Mzg3MTY3NzUwMQ==&mid=2247489155&idx=1&sn=3ce9c3f068c8d7388ea5f5c43cd67f7c&chksm=cf138d8762d4758cc0316b0cfaecf03f529d0872746d29b519864c9df5bf808f92644a2db7d4&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh