前言

今天本着学习研究的态度,下载了一个滴答清单PC pj版,发布日期大概是19年4月左右。

下载之后登录时却遇到了问题,输完账号密码点击登录,然后就会提示Login_OutDate,大概就是说用户不是VIP了,不能用,请重新登录

由于比较好奇为什么之前能用,现在不能用了,于是逆了一下代码,然后就有了这篇文章,成功的绕过了登录后弹窗的问题,以及无法同步的限制

正文

首先根据关键字定位到弹窗代码

image-20200112010250391.png

可以从上图看到,程序弹窗之后就会关闭当前窗体,重置界面登录状态,显示登录窗体,那么我们就可以通过return大法很轻松的让程序不退出登录并继续执行

进行修改后,我们继续分析函数关系,找到上级调用,看看还有没有其他检测

image-20200112010259897.png

可以看到,上面其实也是对当前登录的用户信息进行了一个重置的操作的,所以这边也是要继续return掉的

保险起见继续回溯,看上级调用

image-20200112010250391.png

从这里可以看出来,程序请求了滴答清单的API接口来获取用户信息,如果检测到用户非法,就执行刚刚我们找到的那一系列函数进行登出操作。

那么做到这一步之后,就解决了PC端登录秒退的问题,用户已经可以正常本地使用原破解版的所有功能了,但目前还不能同步,会提示网络错误

所以我们继续看为什么会走到:response.StatusCode == HttpStatusCode.Unauthorized,能否通过修改判断逻辑实现数据同步

为了方便大家理解,我把上面那张图中所在函数的关键逻辑贴出来

嫌乱的可以跳过这部分代码,我一会还会用文字解释

            HttpClient httpClient = ProxyHelper.GetHttpClient();
            httpClient.DefaultRequestHeaders.Add("Authorization", "OAuth " + **auth**);
            httpClient.DefaultRequestHeaders.Add("User-Agent", "TickTickClient/1.0");
            httpClient.DefaultRequestHeaders.Add("x-device", Utils.GetDeviceInfo());
            string domain = BaseUrl.GetDomain();
            string uriString;
            if (!**fulluri**)
            {
              uriString = domain + **api**;
            }
            else
            {
              uriString = **api**;
            }
            string mode2 = **mode**;
            if (!(mode2 == "POST"))
            {
              if (!(mode2 == "GET"))
              {
                if (!(mode2 == "PUT"))
                {
                  if (mode2 == "DELETE")
                  {
                    response = httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Delete, new Uri(uriString))
                    {
                      Content = new StringContent(**content**, Encoding.UTF8, "application/json")
                    }).Result;
                  }
                }
                else
                {
                  response = httpClient.PutAsync(new Uri(uriString), new StringContent(**content**, Encoding.UTF8, "application/json")).Result;
                }
              }
              else
              {
                response = httpClient.GetAsync(new Uri(uriString)).Result;
              }
            }
            else if (**paramList** == null || **paramList**.Count == 0)
            {
              response = httpClient.PostAsync(new Uri(uriString), new StringContent(**content**, Encoding.UTF8, "application/json")).Result;
            }
            else
            {
              response = httpClient.PostAsync(new Uri(uriString), new FormUrlEncodedContent(**paramList**)).Result;
            }
            if (response != null && (response.StatusCode == HttpStatusCode.OK | **isNeedErrorReturn**))
            {
              responseReturn = response.Content.ReadAsStringAsync().Result;
              if (App.ProExpiredSyncError)
              {
                App.ProExpiredSyncError = false;
              }
            }
            else if (response != null && response.StatusCode == HttpStatusCode.InternalServerError)
            {
              responseReturn = response.Content.ReadAsStringAsync().Result;
              if (!string.IsNullOrEmpty(responseReturn))
              {
                NetWork.HandleApiError(responseReturn);
              }
            }
            else if (response != null && response.StatusCode == HttpStatusCode.Unauthorized)
            {
              Utils.TokenOutDate();
            }

这段代码大概说了个啥呢?其实逻辑很简单,就下面4个步骤:

  1. 获取当前设备信息
  2. 获取需要同步的数据(比如新增、删除、修改待办事项这些)
  3. 将需要同步的数据和当前设备信息发送到滴答清单的远程API接口
  4. 根据远程API返回判断是否同步成功

总的来说,这些逻辑是通过传参数到服务端进行校验,本地并没有做会员状态检测,直接调用的服务端API接口

但!滴答清单有一个比较特殊的地方,他的iOS端是可以免费同步数据的, 没有VIP的限制

也就是说,我们或许可以通过修改电脑端发出的请求,使服务端API认为我们是一个iOS APP,这样我们就可以绕过检测直接同步了

通过APP抓包对比,我发现只需要修改:

httpClient.DefaultRequestHeaders.Add("x-device", Utils.GetDeviceInfo());

为:

httpClient.DefaultRequestHeaders.Add("x-device", “手机端UA”);

即可模拟成手机请求

修改的过程中我发现其实原破解作者也是相同的思路,他修改了Utils里的GetDeviceInfo函数,强制返回一个iPad的UA,按理说是可以同步的(并且前几个月确实能正常使用)

那么我可以做出合理推测,原作者发布破解版本后,被大家广泛使用,最后滴答清单官方团队取得了破解样本,进行分析后,对该UA进行了拉黑处理

也就是说——目前网上流传的那些破解版本没准都是出自一人之手。。所以一个UA封掉之后全部不能用了

总而言之,通过修改UA的值,即可绕过服务端API检测,实现数据同步

后记

回顾了一下整个流程,其实官方应该是拉黑了破解版的UA,所以只要换掉UA,应该就不会出现异常检测了,也就是说不需要去在函数内做return。

如果觉得我的文章对你有用,请随意赞赏