maozhenyu 发表于 2017-5-12 13:52

QQLite的奇葩破解之法——DNS+WebServer

本帖最后由 maozhenyu 于 2017-5-12 14:08 编辑

最近回国了,有点空。于是再来一篇,是我之前完成的QQLite的破解。

不得不说这个东西确实做的很好,几乎实现了QQ协议所有的功能:包括不限于正常的发消息、图片、表情,甚至包括发红包等等。

程序的基本原理是QQLite 调用QQLite.Framework.dll。核心在QQLite.Framework.dll。因此我的目标是自己制作一个“外壳”,调用QQLite.Framework.dll 来实现我的功能。

如果你准备直接引用dll,使用命名空间,………………,然后……   哦,你想得美!

说明里面有联网认证

首先用抓包软件抓取这个QQLite在验证时的网络请求。发现这样的请求:POST http://auth.qqlite.cn/Authorize.php




请求返回了一堆16进制小写字母,怀疑是被AES加密。那么我们着手分析程序。


可惜这东西是用最新的DNGuard HVM 的企业版加的壳,破解起来十分的有难度——至少我没这个时间和耐心去破。好在52pojie有提供过DNGuard HVM Unpacker for 3.71 trial 的版本,虽然无法完全脱开QQLite的壳,但是可以去掉很大一部分。通过搜索,我们发现了在这里。


private readonly string[] ABIONX4Oko = new string[] { "http://qqlite.94qing.com/Authorize.php", "http://auth.94qing.com/Authorize.php", "http://www.qqlite.cn/Authorize.php" };
string str = "无法访问服务器,请确定联网状态";
//注意 为了可读性 我对这里的变量名和方法名进行了一些修改
private bool AuthHosts() //这里读取hosts文件,看看有没有对qqlite域名劫持(常规题,改127.0.0.1然后伪造认证服务器)
      {
            string path = string.Format(@"{0}\drivers\etc\hosts", Environment.GetFolderPath(Environment.SpecialFolder.System));
            if (System.IO.File.Exists(path))
            {
                StreamReader reader = new StreamReader(path, Encoding.Default);
                string str2 = reader.ReadToEnd();
                reader.Close();
                if (((str2.IndexOf("qqlite", StringComparison.CurrentCultureIgnoreCase) > -1) || (str2.IndexOf("94qing", StringComparison.CurrentCultureIgnoreCase) > -1)) || (str2.IndexOf("authorize", StringComparison.CurrentCultureIgnoreCase) > -1))
                {
                  return false;
                }
            }
            return true;
      }

public void MainAuth(){
            if (!this.AuthHosts())
            {
                if (this.DtaOyWwSSY() == null)
                {
                  this.WIxO7NaA7k(new AuthorizeJson());
                }
                this.DtaOyWwSSY().result = false;
                this.DtaOyWwSSY().message = str;
//如果hosts验证不通过,Boom!
            }
            else if (Utility.Md5((License.AgentQQ + License.SoftWareQQ) + License.SoftWareName + License.SoftWareAuthor) != License.LdffLjAU3r()) //这里是读取License检验程序防止篡改的
            {
                str = "非法篡改";
                if (this.DtaOyWwSSY() == null)
                {
                  this.WIxO7NaA7k(new AuthorizeJson());
                }
                this.DtaOyWwSSY().result = false;
                this.DtaOyWwSSY().message = str;
            }
            else //这里开始向服务器发送请求
            {
                byte[] buffer = Utility.Md5File(Splash.GetType().Assembly.Location); //先获得当前dll的md5
                string str3 = string.Empty;
                string str4 = string.Empty;
                foreach (byte num2 in buffer)
                {
                  str3 = str3 + num2.ToString("X2");//先获得当前dll的md5
                }
                foreach (byte num4 in Utility.Md5File(Application.ExecutablePath)) //获得当前宿主exe的md5
                {
                  str4 = str4 + num4.ToString("X2");
                }
                object[] args = new object[] { str3, License.FrameworkVersion, str4, License.Version, this.Jw5OeqK15o().QQ, (this.Jw5OeqK15o().LoginStatus == LoginStatus.Login) ? "1" : "0", bool_0 ? "1" : "0", License.SoftWareQQ, License.AgentQQ, License.FrameworkType };
                string str5 = string.Format("FrameworkMd5={0}&FrameworkVersion={1}&ExeMd5={2}&ExeVersion={3}&RobotQQ={4}&IsLogin={5}&IsReStart={6}&BuildQQ={7}&AgentQQ={8}&FrameworkType={9}", args); //传参
                string str6 = QIDOLuDhqR();
                if (!string.IsNullOrEmpty(str6))
                {
                  str5 = str5 + "&AuthKey=" + str6;
                }
                DateTime now = DateTime.Now;
                int num5 = now.Minute + 10;
                string str7 = num5.ToString(CultureInfo.InvariantCulture);
                string str8 = (now.Second + 10).ToString(CultureInfo.InvariantCulture);
                string str9 = str7 + str8;
                string key = Utility.Md5(string.Concat(new object[] { this.Jw5OeqK15o().QQ, "00-01-6C-06-A6-29", License.FrameworkVersion.Replace(".", ""), str9 }));
                object[] objArray3 = new object[] { this.Jw5OeqK15o().QQ, License.FrameworkVersion.Replace(".", ""), "00-01-6C-06-A6-29", str9 };
                string str11 = Utility.Md5(string.Concat(objArray3)).Substring(5, 0x10);
                str5 = PHP.authcode_en(str5, key);
//这里计算authcode
                object[] objArray4 = new object[] { jb9BOoatwNaPbmWQJN.FCsPZSC4Oe(now) / 0x3e8L, License.FrameworkVersion, this.Jw5OeqK15o().QQ, HttpUtility.UrlEncode(str5) };
                string str12 = string.Format("Do=CheckAuthorize&AuthVersion=2.3&Time={0}&Version={1}&RobotQQ={2}&Data={3}", objArray4);
                HttpHelper helper = new HttpHelper();
                HttpItem item = new HttpItem {
                  URL = this.ABIONX4Oko,
                  Method = "POST",
                  UserAgent = "QQLite_Framework_" + License.FrameworkVersion,
                  Postdata = str12
                };
                HttpResult html = helper.GetHtml(item);
//发出HTTP请求,并获得返回
                if (((html.StatusCode == HttpStatusCode.OK) && !string.IsNullOrEmpty(html.Html)) && (html.Html.Length >= 5))
                {
                  string str13 = MdTXO0IDbFmn8RvdCTm.vdTweSWh6L(html.Html, str11);、、
//这里验证返回值
                  if (!string.IsNullOrEmpty(str13) && (str13.Length >= 5))
                  {
                        string str14 = PHP.authcode_de(str13, key);
                        if (!string.IsNullOrEmpty(str14) && (str14.Length >= 5))
                        {
                            AuthorizeJson json = Json.Deserialize<AuthorizeJson>(str14);
                            if (json == null)
                            {
                              if (this.DtaOyWwSSY() == null)
                              {
                                    this.WIxO7NaA7k(new AuthorizeJson());
                              }
                              this.DtaOyWwSSY().result = false;
                              this.DtaOyWwSSY().message = str;
                            }
                            else
                            {
                              this.WIxO7NaA7k(json);
                              if (this.DtaOyWwSSY().authkey != null)
                              {
                                    object[] objArray5 = new object[] { this.Jw5OeqK15o().QQ, License.Version.Replace(".", ""), str9, "00-01-6C-06-A6-29" };
                                    if (this.DtaOyWwSSY().authkey == Utility.Md5(string.Concat(objArray5)))
                                    {
                                        if (!string.IsNullOrEmpty(this.DtaOyWwSSY().message))
                                        {
                                          this.DtaOyWwSSY().message = HttpUtility.UrlDecode(this.DtaOyWwSSY().message);
                                        }
                                        this.DtaOyWwSSY().authkey = null;
                                        return;
                                    }
                              }
                              if (!string.IsNullOrEmpty(this.DtaOyWwSSY().message))
                              {
                                    this.DtaOyWwSSY().message = HttpUtility.UrlDecode(this.DtaOyWwSSY().message);
                              }
                            }
                        }
                        else if (int_0 == 0)
                        {
                            this.fE8OvhvTfu(bool_0, int_0 + 1);
                        }
                        else
                        {
                            if (this.DtaOyWwSSY() == null)
                            {
                              this.WIxO7NaA7k(new AuthorizeJson());
                            }
                            this.DtaOyWwSSY().result = false;
                            this.DtaOyWwSSY().message = str;
                        }
                  }
                  else if (int_0 == 0)
                  {
                        this.fE8OvhvTfu(bool_0, int_0 + 1);
                  }
                  else
                  {
                        if (this.DtaOyWwSSY() == null)
                        {
                            this.WIxO7NaA7k(new AuthorizeJson());
                        }
                        this.DtaOyWwSSY().result = false;
                        this.DtaOyWwSSY().message = str;
                  }
                }
                else if (html.StatusCode != HttpStatusCode.NotFound)
                {
                  this.fE8OvhvTfu(bool_0, int_0 + 1);
                }
                else
                {
                  if (this.DtaOyWwSSY() == null)
                  {
                        this.WIxO7NaA7k(new AuthorizeJson());
                  }
                  this.DtaOyWwSSY().result = false;
                  this.DtaOyWwSSY().message = str;
                }
            }

这里就是认证的核心代码了,我在上面打了注释。

对于这种问题的通常解决方法是根据代码做一个Emulator,伪造的认证服务器。然后通过hosts把向原本认证服务器的请求转移到伪造的服务器上。

可惜,他做了hosts检查,没法用hosts了。

好啊,你不让我用hosts,我用DNS还不行么?立刻动手本地搭一个DNS服务器,C#处理,代码如下:


//这里我使用了ARSoft.Tools的库(http://arsofttoolsnet.codeplex.com/ )
public MyDNSServer()
      {
            DnsServer dnsServer = new DnsServer(IPAddress.Parse("0.0.0.0"), 1, 1); //监听本地DNS
            dnsServer.QueryReceived += DnsServer_QueryReceived;
            dnsServer.Start();//this.ProcessQuery
      }

      private async System.Threading.Tasks.Task DnsServer_QueryReceived(object sender, QueryReceivedEventArgs e) //Handle 请求
      {
            DnsMessage query = e.Query as DnsMessage;

            if (query == null)
                return;

            DnsMessage response = query.CreateResponseInstance();
            string add = query.Questions.Name.ToString();

            // check for valid query
            if ((query.Questions.Count == 1)
                && (query.Questions.RecordType == RecordType.A)
               )
            {
                if (add== "auth.qqlite.cn.") //如果是对auth.qqlite.cn域名的DNS请求
                {
                  response.AnswerRecords.Add(new ARecord(query.Questions.Name, 36000, IPAddress.Parse("127.0.0.1"))); //给他返回A记录 127.0.0.1,也就是本机
                  response.ReturnCode = ReturnCode.NoError;
                }
            }

            // set the response
            e.Response = response;
      }

修改主用DNS为127.0.0.1,备用DNS为你的上级DNS。

那我同时就用这个程序搭建Emulator了,代码截图:


QQLite请求时,我的Emulator的显示:


QQLite主程序能工作了:


主要问题解决了,是不是呢

56766876 发表于 2017-10-26 23:00

你好 ,咨询下 “ QQLite请求返回了一堆16进制小写字母,怀疑是被AES加密”,这里的AES我解密结果这么只有部分,还有个尾巴 消失了
他是用的是aes哪种填充方式加密的呢?

aes_key=f81c0a4a5d6b43b9
密文=6cda8ef7094bbd60a3f39564f3c8c6b750234511d6272f372d5ef4899a21ec1f720c0d0a4cfc725a15d6bc6bd8b255311acc92f68cfecd9925009a0299c2c67195e8436b3020dade5c4a5412bc41183c9c32d3c7f3278ec621fe6440cca9fddac96d6751e15881afb388c3afe8a2f9bb2a3671d822a51d90b8a8f4c7a220b5dc00eb274aa5475266a3d7facddf23cf13a50fde56224a1fd6da3b9e82713a91813e8357610af9d75aa911e9c838e2aac3ccef435ad0dd73f7804a9e3022b8d93c5f71bab666ed7195b7c1ed074c31c714a911211185c2ed91950e420fc834fed74cf59e7240c0bcffea83df515b77563049b66c50fe44f2d1274e875be1a4f2cca0f7b79ba0d04cb872fe5dbc161bc663d66106222602f12b2534dc5554d316b8a0f1594da6ecd1de5a5ff2ceb36826693e5f48684995f155cc111b632e2f67caae052ee7be5782fc068f6bd882fec0a9551c7c884d2582ec16b731533ebf66485f2bcb0eeef2d72277a9165843fd344d4ae4fa56f2027ba4846f9a089b2cbc762ae463baa9ea2542627e94783e4ea08ec11313a8f74c1b4ea3adcfa6ef76dec70af9817135d0c28dd163cf130e34161c23396ff8eeb1aa0b2f8676e77019d14a9ca0cdc8e38c0f4cb9d54471d7a5b8b29e9551004f798e1943ab9c1628fcf66ab4c427ea1246791480a748736f576a902983214bb44eebf824534c5ecd766d0fd739566ace91cd077152bc9ce387b3eb72a7eb0cb804926122d31b1a03bf38119ad4e3c91db1ed1b2f17a7c0847ab98f4879a3c931e510027dfae4ff6fb5afd6ccb97b926d085877a25fd7985dd769bf5fbc5e34d6a218f2a68c217301fdb5ecec6c62439e91034dea45f1c0628988401b47992eaa70a4216aabb6c019503ef404a62208c13f3587c3d7c7f3164fc52e

我aes解密的结果是 2a34BQPe5ORETeurQeeeL/Sftw7POvvRpZnxYyYrwdWHb3bLUcliYAlMUoF8b5oi++9QCAV6mUxFAbnDuAFGbUSSFT4MFNCIZQSPLsTP/pJMlU+H4FNV+GT0k9Yxvn7NKHDqGxUZon0lEQGTVOSHSaQAXUwe6XSs/02uI1lD34+YCtpUapKJE8jyIj7rovh7r6OER0/N0Yjj+2D9EkeZs3jlE5Bbmg+t3mYvQJzj8ocZKj4c47Nq+gKpQDH/Lm5JXFV9Bmm5LIJMTEFg0FXVSyDtGRpz6dDs5u35Bhxwy2Rxd3R4wWaFcU2niiJynGCdytLQIdCWuebicCud7f0NXsngYEH7JqHvfxF+kgHX3hy03YHw0JZIxouas/YbzXO0Fe4kYRa6uBxBVpieurqvzu/+UJilW+EaVyE3W8km2zUpVoWuKQrTybVcPTXsou2+O7YoeL2WLMt7w/DVU3Q6vk/FHVATVKNw75uyDPPDoyGYDQMDwZHWhPBqtVzkFgTMpMGeuoJ8m91XvO6aVv5hMkUQiME9hhjcsDZ1HjGaZTz5GnXashZ6wEWKilg+MTqigdgYSNCiQHPhdtTmNd7B//mFVfWOLTO72A7yJoCiZJOHCn5Y

正确的结果是 2a34BQPe5ORETeurQeeeL/Sftw7POvvRpZnxYyYrwdWHb3bLUcliYAlMUoF8b5oi++9QCAV6mUxFAbnDuAFGbUSSFT4MFNCIZQSPLsTP/pJMlU+H4FNV+GT0k9Yxvn7NKHDqGxUZon0lEQGTVOSHSaQAXUwe6XSs/02uI1lD34+YCtpUapKJE8jyIj7rovh7r6OER0/N0Yjj+2D9EkeZs3jlE5Bbmg+t3mYvQJzj8ocZKj4c47Nq+gKpQDH/Lm5JXFV9Bmm5LIJMTEFg0FXVSyDtGRpz6dDs5u35Bhxwy2Rxd3R4wWaFcU2niiJynGCdytLQIdCWuebicCud7f0NXsngYEH7JqHvfxF+kgHX3hy03YHw0JZIxouas/YbzXO0Fe4kYRa6uBxBVpieurqvzu/+UJilW+EaVyE3W8km2zUpVoWuKQrTybVcPTXsou2+O7YoeL2WLMt7w/DVU3Q6vk/FHVATVKNw75uyDPPDoyGYDQMDwZHWhPBqtVzkFgTMpMGeuoJ8m91XvO6aVv5hMkUQiME9hhjcsDZ1HjGaZTz5GnXashZ6wEWKilg+MTqigdgYSNCiQHPhdtTmNd7B//mFVfWOLTO72A7yJoCiZJOHCn5YmombKZ+FdYg8evHMcQ/AAUQQ

多了mombKZ+FdYg8evHMcQ/AAUQQ这尾巴
就这搞不定了。谢谢

wtuaixk 发表于 2017-5-12 13:59

沙发沙发。{:301_997:}

抹不掉 发表于 2017-5-12 14:12

求破解版{:301_997:}

wt7758521888 发表于 2017-5-12 14:14

大佬厉害!

大象无形 发表于 2017-5-12 14:27

zhminxp 发表于 2017-5-12 14:38

贴代码啊。老大。学习一下

mayl8822 发表于 2017-5-12 14:41

厉害了哥, 感谢分享

chinaboy008 发表于 2017-5-12 15:02

厉害!支持你!!

师法自然 发表于 2017-5-12 15:20

厉害厉害,这个思路可以啊

maoxuechuen 发表于 2017-5-12 15:22

完全看不明白!还是支持楼主分享
页: [1] 2 3 4 5 6 7
查看完整版本: QQLite的奇葩破解之法——DNS+WebServer