关于tagLyst工具授权验证的分析报告

查看 107|回复 10
作者:Silent_DXX   
破解前的软件界面


892d5c1c-273d-4ca6-b03e-b06dca475028.jpg (164.1 KB, 下载次数: 3)
下载附件
2020-4-24 11:57 上传

找到安装目录
tagLyst是基于electron技术开发的工具,使用这种技术的软件很容易会被解包并查看到程序代码。
在tagLyst\run\resources目录下,我们可以看到如下几个文件:
app.asar
default_app.asar
electron.asar
有关程序逻辑的主要代码在app.asar中
app.asar 解包
electron程序在发布的时候会通过asar工具对源文件进行打包。首先通过下面的命令安装asar工具(需要提前安装nodejs环境)
npm install -g asar
下面我们进行解包操作
asar extract app.asar ./app
通过这个命令把app.asar文件解包到./app下面
代码格式化
解包之后虽然我们能够看到代码,但是代码格式上却乱七八糟的,通过安装下面的工具对源码进行格式化操作
npm install --g prettier
安装成功后, 把我们可能需要分析的源码进行格式化
prettier --write app/main.js
prettier --write app/
开启调试功能
打开package.json文件
{
  "name": "tagLyst Next",
  "version": "3.1.0",
  "main": "main.js",
  "dependencies": {
    "request": "^2.88.0"
  }
}
通过查看package.json文件,得知主程序文件为main.js
我们在main.js文件中找到createWindows函数,然后在合适的位置上添加下面的代码来启动调试功能。调试功能启用之后我们就可以像调试前端的方式去调试tagLyst工具,并分析其破解过程。
function createWindows() {
...
    mainWindow.webContents.openDevTools();
...
}
注册分析
打开tagLyst工具,点击上面的购买升级按钮,随便填入一个邮箱和一串激活码,并点击在线激活按钮,我们发现会注册失败,对话框提示激活码并不正确。


17b93f17-5c0b-4cdb-8c9d-b743fefcb9ba.png (66.56 KB, 下载次数: 1)
下载附件
2020-4-24 11:59 上传



6e1f3243-2b8e-4f7c-9e23-cf258bb30248.png (44.75 KB, 下载次数: 1)
下载附件
2020-4-24 11:59 上传

cd tagLyst\run\resources\app
grep "激活码并不正确" . -rn
这里使用grep工具搜索关键词(你可以使用任何搜索工具来操作)


4afe60f4-41ba-4306-aae8-5d769ccce421.png (227.96 KB, 下载次数: 1)
下载附件
2020-4-24 12:00 上传

这里我们找到了很多个选择,怎么确认哪一个才是呢,我也没有很好的办法,只能适当修改下搜索到的地方,并测试查看输出。


b2a98f17-f08e-4f4e-96f6-702673c87f43.png (110.91 KB, 下载次数: 1)
下载附件
2020-4-24 12:00 上传

一次次尝试,很快就找到了地方,就是在app1.js的Zc3baa406ebb4e97e02569e3c6e7a7a93中
Zc3baa406ebb4e97e02569e3c6e7a7a93 = {
...
      MSG_ACTIVATION_FAIL_DENIED:
        "很遗憾!您输入的激活码并不正确 xxxx。请检查拼写。
如有问题,请联系我们获得支持。",
...
}
我们继续搜索MSG_ACTIVATION_FAIL_DENIED
grep "MSG_ACTIVATION_FAIL_DENIED" . -rn
随后我们看到一个很关键的地方


ecce6b22-e4ac-4a9f-87d2-61262fa32c15.png (155.63 KB, 下载次数: 1)
下载附件
2020-4-24 12:01 上传

而这段代码的else部分是这样的


3559bee2-c127-4e4d-869e-7ee9f4c9ef81.png (108.96 KB, 下载次数: 1)
下载附件
2020-4-24 12:01 上传

所以这个变量e就是成功的关键,那么这个e是怎么过来的呢?
答案是runActivation
我们在runActivation上面添加一个断点或者使用debugger
debugger;


c78e20f5-fe68-4b99-995d-f4d5c9e2d79d.png (221.85 KB, 下载次数: 1)
下载附件
2020-4-24 12:02 上传

一步一步执行到runActivation函数里面


93cd478a-8a80-4a44-b177-bac1eba0c188.png (156.67 KB, 下载次数: 1)
下载附件
2020-4-24 12:02 上传

服务器返回数据


25c4ec30-3936-40d8-9100-18c057ccf690.png (94.26 KB, 下载次数: 1)
下载附件
2020-4-24 12:02 上传

经过分析,我大概理解了这个代码,下面用注释的方式来解释:
function runActivation(e, t, n) {
  // e: 邮箱地址
  // t: 激活码
  // n: 返回函数
  try {
    saveActivationEvidence(
      "evidence1",
      "from " + new Date(fs.statSync(__exePath).birthtime).format("yyyyMMdd")
    );
  } catch (e) {}
  try {
    saveActivationEvidence("evidence2", JSON.stringify(tglData.dashboard));
  } catch (e) {}
  if (SSO.noTrack) return console.log("No Track for ACT");
  var a = nz(e, "").toLowerCase(),
    i = nz(t, {});
  try {
    var l = nz(n, function () {}),
      s = getDeviceInfo(a),  // 获取设备ID信息
      o = s.deviceID,
      r = s.deviceTextEnc,
      c = require("http"),   // 发送POST请求使用
      d = {                  // 把机器信息组包为json数据
        data: JSON.stringify({
          activationCode: a,
          deviceTextEnc: r,
          deviceID: o,
          userID: nz(i.userID),
          tag1: i.tag1,
          tag2: i.tag2,
          tag3: i.tag3,
          evidence1: loadActivationEvidence("evidence1", ""),
          evidence2: loadActivationEvidence("evidence2", ""),
          evidence3: loadActivationEvidence("evidence3", ""),
        }),
      };
    d = JSON.stringify(d);
    // 构建POST请求数据
    var f = {
        method: "POST",
        host: Pd5e58e4fab1ba92ff7d0bb4ce2459058.activationHost, // activation.taglyst.com
        port: Pd5e58e4fab1ba92ff7d0bb4ce2459058.activationPort, // 12050
        path: "/activate.do",
        headers: {
          "Content-Type": "application/json",
          "Content-Length": d.length,
        },
      },
      // 这个函数很关键,它是发送POST请求服务器的回复数据
      // 当激活码无效时的返回数据{"error":"denied"}
      // denied 是不是比较眼熟?没错就是提示激活码不正确前的那个e的值
      g = function (e) {
        try {
          console.log('request: ' + e);
          var t = JSON.parse(e);        // 服务器返回的数据
            n = nz(t.userID, "");       // 用户ID
            i = nz(t.expiringDate, ""); // 激活码到期日
            s = nz(t.error, "");        // error为denied
            // 下面就是判断是不是Business的版本,该值是由服务器返回,并把数据写入到配置文件中
          (Pd5e58e4fab1ba92ff7d0bb4ce2459058.bizc = nz(t.isBusinessClass, 0)
            ? 1
            : 0),
            Pd5e58e4fab1ba92ff7d0bb4ce2459058.bizc
              ? writeProjectData("vcls", "bizc")
              : removeProjectData("vcls");        // 把bizc写入到[计算机名.vcls]文件中
          nz(t.userRole, "").toLowerCase();
          var o = nz(t.teamID, "");  // teamID 暂时没有理解是干啥的
          // 下面就是保存数据并赋值服务器的数据
          o &&
            (writeProjectData(
              "temd",
              nz(Pd5e58e4fab1ba92ff7d0bb4ce2459058.temd, "")
            ),
            (Pd5e58e4fab1ba92ff7d0bb4ce2459058.temd = o)),
            n &&                             // 保存用户ID
              (writeProjectData("usrd", n),
              (Pd5e58e4fab1ba92ff7d0bb4ce2459058.usrd = n)),
            i &&
              ((Pd5e58e4fab1ba92ff7d0bb4ce2459058.avdt = new Date(i)),
              (Pd5e58e4fab1ba92ff7d0bb4ce2459058.actd = 1),
              (Pd5e58e4fab1ba92ff7d0bb4ce2459058.lckd = 0),
              ckacv2 && ckacv2(),
              writeProjectData("avdt", i),   // 保存到期时间
              writeProjectData("actc", a)),  // 保存邮箱地址
            s &&
              "locked" == s &&               // 感觉是服务器封杀用户用的
              ((Pd5e58e4fab1ba92ff7d0bb4ce2459058.lckd = 1),
              writeProjectData("actc", ""),
              writeProjectData(
                "avdt",
                new Date("2001-1-1").format("yyyy-MM-dd")
              )),
            l(s);                           // 执行回调函数
        } catch (e) {
          console.log(e);
        }
      };
    if (i.isOffline) {
      try {
        var u = parseOfflineActivationCode(a, o);
      } catch (e) {
        l("offlineFailed");
      }
      return (
        cerr("OfflineActivationResData", JSON.stringify(u)),
        void g(JSON.stringify(u))
      );
    }
    (req = c.request(f, function (e) { // 发送POST请求
      if (!a)
        return console.log(
          "* Activation Res will not work at startup without ActivationCode."
        );
      e.setEncoding("utf8"),
        e.on("data", g),          // 服务器回复数据,并调用上面的g函数
        e.on("end", function () {
          console.log("res ends.");
        }),
        e.on("error", function (e) {
          console.error("res error:", e), l("failure");
        });
    })),
      req.setTimeout(1e4, function () {
        console.log("req timed out"), l("failure");
      }),
      req.on("error", function (e) {
        console.log("req error", e), l("failure");
      }),
      req.write(d),
      req.end();
  } catch (e) {
    console.log(e);
  }
}
构思破解
[ol]
  • 当我们点击在线激活按钮时,会发送一个POST数据到http://activation.taglyst.com:12050/activate.do服务器中,这些数据包含邮箱号,激活码,机器ID等信息。
  • 随后服务器会返回一个json数据,如果激活码是无效的就返回    {"error":"denied"}, 否则就返回正确的验证数据
  • 所以我们可以尝试在服务器返回时,构造一个假数据来欺骗软件说服务器通过验证了,正确的验证信息是某某某
    [/ol]
    尝试破解
    ...
          g = function (e) {
            try {
              console.log('request: ' + e);
              var t = JSON.parse(e);
                t.userID = "52pojie";        // 构造假ID
                t.expiringDate = "2999/1/1"; // 到期时间
                t.error = "";                // 清空error
                t.isBusinessClass = 1;       // 商业版本
                n = nz(t.userID, "");
                i = nz(t.expiringDate, "");
                s = nz(t.error, "");
              console.log("avdt:", i, s);
    ...
    通过测试居然发现成功的破解了软件!!令人诧异。。。一个简单的欺骗就结束了??
    重新打包
    asar pack app app.asar
    破解成果图


    49ec0b3e-404e-45f1-8d49-3f02a4607ae0.jpg (150.27 KB, 下载次数: 3)
    下载附件
    2020-4-24 12:03 上传

    完结,撒花~~~
    后语
    通过此次分析,我们可想而知软件安全的重要性,开发者在考虑软件授权时应该要考虑的更多一些才行。tagLyst虽然使用了服务器验证,但是返回数据居然是json,而且是没有加密的数据,这是一个很严重的问题。
    对于此次分析,我给出的建议是:
    [ol]
  • 软件授权部分不应该使用容易被还原代码的语言设计(例如:JavaScript、python、java等),而且使用c/c++之类不容易被还原代码的语言设计(electron支持c语言开发的模块)
  • 网络授权的api应该要加密数据,最好采用非对称加密方式来提高数据安全
  • 授权部分的代码采用代码混淆技术来提高被反破解的难度
  • 授权代码不仅仅采用一个api来验证,最好采用不用的接口在不同的地方或时间来验证
  • c/c++语言设计授权验证,可以添加反调试机制、花指令、动态代码等多种方式来提高安全性
    [/ol]

    下载次数, 数据

  • uzcool   


    20150605101006 发表于 2022-3-3 12:47
    tagLyst大佬求一下,我一直pj不成功

    试了下最新的版本好像还可以..
    https://uzcool.118pan.com/b1078834
    https://uzcool.118pan.com/b1078835
    替换之后需要输入下邮箱和随便输入个注册码应该就行了


    Snipaste_2022-03-05_01-59-26.jpg (66.06 KB, 下载次数: 0)
    下载附件
    2022-3-5 02:00 上传

    麦米尔加弗德   

    最新版本的 tagLyst ES 也就是 tagLyst 5
    方法需要一点小改动,来交作业了,代码写的很好
    https://wwk.lanzoue.com/b03vg9hna
    密码:hq3q
    blsn3548   

    这是什么语言写的软件?
    Silent_DXX
    OP
      


    朱朱你堕落了 发表于 2020-4-24 15:28
    这是什么语言写的软件?

    应该是node.js
    冥界3大法王   


    朱朱你堕落了 发表于 2020-4-24 15:28
    这是什么语言写的软件?

    语言是JavaScript,技术是nodejs+electron实现的,有兴趣可以搜素electron,跨平台的桌面应用开发。
    Silent_DXX
    OP
      

    也就是说还是修改的js文件?
    动态调试和伪造数据是关键的两步。
    bingmoer   


    冥界3大法王 发表于 2020-4-24 16:35
    也就是说还是修改的js文件?
    动态调试和伪造数据是关键的两步。

    修改js文件是一种办法,一劳永逸。也可以伪造一个服务器来完成欺骗。
    bovod   

    按大神思路操作,打开后是空白,无法进入程序,不知道是哪里出错了。
    [i]
    斩风   

    学习下,感谢分享
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部