Silent_DXX 发表于 2020-4-24 12:20

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

本帖最后由 Silent_DXX 于 2020-4-24 12:22 编辑


# 破解前的软件界面



# 找到安装目录
`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`工具,点击上面的`购买升级`按钮,随便填入一个邮箱和一串激活码,并点击`在线激活`按钮,我们发现会注册失败,对话框提示激活码并不正确。



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

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

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

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

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

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

服务器返回数据

经过分析,我大概理解了这个代码,下面用注释的方式来解释:
```
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);
}
}
```

# 构思破解
1. 当我们点击`在线激活`按钮时,会发送一个POST数据到`http://activation.taglyst.com:12050/activate.do`服务器中,这些数据包含邮箱号,激活码,机器ID等信息。
2. 随后服务器会返回一个`json`数据,如果激活码是无效的就返回    `{"error":"denied"}`, 否则就返回正确的验证数据
3. 所以我们可以尝试在服务器返回时,构造一个假数据来欺骗软件说服务器通过验证了,正确的验证信息是某某某

# 尝试破解
```
...
      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
```

# 破解成果图

完结,撒花~~~

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

uzcool 发表于 2022-3-5 01:57

本帖最后由 uzcool 于 2022-3-5 02:00 编辑

20150605101006 发表于 2022-3-3 12:47
tagLyst大佬求一下,我一直pj不成功
试了下最新的版本好像还可以..
https://uzcool.118pan.com/b1078834
https://uzcool.118pan.com/b1078835
替换之后需要输入下邮箱和随便输入个注册码应该就行了


麦米尔加弗德 发表于 2022-11-11 18:40

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

古墓卓洛 发表于 2020-5-2 16:41

亲测成功,给大佬点赞!

林小轩112 发表于 2020-4-30 13:09

感谢大佬分享,已经成功。

xieyi1393 发表于 2020-4-25 10:12

其实不需要对称加密,HTTPS公钥扎钉好像就可以

5ipj2007 发表于 2020-4-25 10:00

修改后还是要确保联网,订阅才能激活。
怎么样不联网激活

5ipj2007 发表于 2020-4-25 09:47

改了启动程序后空白

斩风 发表于 2020-4-24 19:02

学习下,感谢分享

bingmoer 发表于 2020-4-24 17:25

Silent_DXX 发表于 2020-4-24 16:45

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

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

朱朱你堕落了 发表于 2020-4-24 15:28

这是什么语言写的软件?

blsn3548 发表于 2020-4-24 15:56

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

应该是node.js

Silent_DXX 发表于 2020-4-24 15:58

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

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

冥界3大法王 发表于 2020-4-24 16:35

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

bovod 发表于 2020-4-24 17:34

按大神思路操作,打开后是空白,无法进入程序,不知道是哪里出错了。
https://ae01.alicdn.com/kf/U0ca613984d634b1a8cad0a31dca7db17n.jpg

blsn3548 发表于 2020-4-24 19:09

Proxifier+fiddler = ok
页: [1] 2 3 4 5 6 7 8 9
查看完整版本: 关于tagLyst工具授权验证的分析报告