CRoot 发表于 2022-2-19 11:14

某MarkDown编辑器在线激活流程简易分析

## 在线激活

注册码本地合法校验:

``` javascript
const Z = e => {
    const r = "L23456789ABCDEFGHJKMNPQRSTUVWXYZ";
    if (!/^({6}-){3}{6}$/.exec(e)) return !1;
    var e = e.replace(/-/g, ""),
      t = e.substr(22);
    return !e.replace(//g, "") && t == (e => {
      for (var t = "", n = 0; n < 2; n++) {
            for (var o = 0, i = 0; i < 16; i += 2) o += r.indexOf(e);
            o %= r.length, t += r
      }
      return t
    })(e)
}
```
使用网传代码轻松过:

``` javascript
function randomSerial() {
    var $chars = 'L23456789ABCDEFGHJKMNPQRSTUVWXYZ';
    var maxPos = $chars.length;
    var serial = '';
    for (i = 0; i < 22; i++) {
      serial += $chars.charAt(Math.floor(Math.random() * maxPos));
    }
    serial += (e => {
      for (var t = "", i = 0; i < 2; i++) {
            for (var a = 0, s = 0; s < 16; s += 2) a += $chars.indexOf(e);
            t += $chars
      }
      return t
    })(serial)
    return serial.slice(0, 6) + "-" + serial.slice(6, 12) + "-" + serial.slice(12, 18) + "-" + serial.slice(18, 24);
}
```
然后发送给远端服务器 `api/client/activate` 提交激活信息
``` javascript
t = {
    v: A() + "|" + s.getVersion(),       //系统平台
    license: t,                        //激活码
    email: e,                            //邮箱
    l: await G(),                        //hostname 以及用户名信息
    f: await M(),                        //机器唯一识别码
    u: s.setting.generateUUID(),         //UUID
    type: global.devVersion ? "dev" : "",//当前环境是否是开发环境
    force: n                           //
};
```

获取机器名以及当前用户名信息:
``` javascript
function G() {
    var o = (o = process.env.USER) || a(842).userInfo().username;
    switch (process.platform) {
    case "win32":
      return process.env.COMPUTERNAME + " | " + o + " | Windows";
    case "darwin":
      return new Promise(n => {
            a(620).exec("scutil --get ComputerName", {
                timeout: 5e3
            }, (e, t) => {
                n(!e && t ? t.toString().trim() + " | " + o + " | darwin" : a(842).hostname() + " | " + o + " | darwin")
            })
      });
    default:
      return a(842).hostname() + " | " + o + " | Linux"
    }
}
```

机器码获取:
``` javascript
const M = async() => {
    if (!i) {
      if (w) {
            const t = C("native-reg");
            var e = t.openKey(t.HKEY.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", t.Access.WOW64_64KEY | t.Access.READ);
            i = t.getValue(e, null, "MachineGuid"), t.closeKey(e)
      } else i = await a(560).machineId({
            original: !0
      });
      i || r.captureMessage(" Failed to get fingerPrint"), i = T(i, "typora").substr(0, 10).replace(/[/=+-]/g, "a"), b && (i += "darwin")
    }
    return i
};
```


然后会根据返回值进行判断
``` javascript
if (JSON.stringify(o.data), console.log(" response code is " + o.data.code), o.data.code == D.SUCCESS) return await Y(o.data.msg) ? [!0, ""] : [!1, "Please input a valid license code"];
if (o.data.code == D.OUT_OF_LIMIT) return n ? await Y(o.data.msg) ? [!0, "Your license has exceeded the max devices numbers.\nThe oldest device was unregistered automatically."] : o.data.msg ? [!1, "Please input a valid license code"] : [!1, "Your license has exceeded the max devices numbers."] : ["confirm", 'Your license has exceeded the max devices numbers.\nIf you click "Continue Activation", this device will be activated and the oldest device will be unregistered automatically.'];
if (o.data.code == D.INVALIDATE) return [!1, "Please input a valid license code"];
if (o.data.code == D.WRONG_USER) return [!1, "This license code has been used with a different email address."]
```

当其返回为`D.SUCCESS`的时候,会写入注册表,保存注册信息

``` javascript
function Y(e) {
    try {
      var {
            fingerprint: t,
            email: n,
            license: o,
            type: i
      } = I(e) || {};
      return t == await M() && n && o ? (H(n, o, i), d().put("SLicense", e + "#0#" + (new Date).toLocaleDateString("en-US")), l = !0) : (console.log(" validate server return fail"), V(), !1)
    } catch (e) {
      throw console.error(e.stack), new Error("WriteActivationInfoFail")
    }
}
```
这里的`H`是本地稍加检测授权,并刷新当前开启的授权状态(有一定猜测在里面),而`l`则是当前全局的激活状态
``` javascript
function H(e, t, n) {
    c = t, S = n, (l = !(!(x = e) || !c)) && re()
}
```
紧接着就将注册信息利用`d()`写入注册表了
``` javascript
const d = function () {
    var n;
    return O = null == O ? w ? function () {
      const o = C("native-reg"),
            i = "Software\\Typora";
      return {
            get: function (e) {
                var t = o.openKey(o.HKCU, i, o.Access.READ);
                if (null == t) return "";
                e = o.getValue(t, null, e);
                return o.closeKey(t), e
            }, put: function (e, t) {
                var n = o.createKey(o.HKCU, i, o.Access.WRITE);
                o.setValueSZ(n, e, t), o.closeKey(n)
            }
      }
    }() : (n = s.setting.prepDatabase(i), {
      put: function (e, t) {
            console.log("ls put " + e), n.getState() = t, n.write()
      }, get: function (e) {
            return n.getState()
      }
    }) : O
};
```
同时这里要注意返回的json数据经过了`I()`进行处理
``` javascript
const I = e => {
    if (!e) return e;
    var t;
    try {
      t = Buffer.from(e, "base64");
      const n = a(289).publicDecrypt(`-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7nVoGCHqIMJyqgALEUrc 5JJhap0+HtJqzPE04pz4y+nrOmY7/12f3HvZyyoRsxKdXTZbO0wEHFIh0cRqsuaJ PyaOOPbA0BsalofIAY3mRhQQ3vSf+rn3g+w0S+udWmKV9DnmJlpWqizFajU4T/E4 5ZgMNcXt3E1ips32rdbTR0Nnen9PVITvrbJ3l6CI2BFBImZQZ2P8N+LsqfJsqyVV wDkt3mHAVxV7FZbfYWG+8FDSuKQHaCmvgAtChx9hwl3J6RekkqDVa6GIV13D23LS qdk0Jb521wFJi/V6QAK6SLBiby5gYN6zQQ5RQpjXtR53MwzTdiAzGEuKdOtrY2Me DwIDAQAB-----END PUBLIC KEY-----`, t);
      return JSON.parse(n.toString("utf8"))
    } catch (e) {
      return null
    }
}
```
这里使用的RSA算法,对返回的授权信息进行解密。

另外,启动的时候有授权合法检测,不合法仍然会掉授权。
``` javascript
const j = e => {
    console.log(" firstValidateLicense"), L = !0;
    var t = W(),
      {
            license: n,
            email: o,
            type: i
      } = t || {};
    n && o ? (H(o, n, i), B(t, e), console.log(" pass validateLicenseInfoStr")) : V()
}
```
里面调用`V()`函数,一般用作授权删除或者清理非法授权使用。相较于最初的验证版本,个人感觉在线验证更加复杂了一些,不过新增了离线激活模式。
``` javascript
function V(e) {
    l || (e = ""), c = x = "", l = !1, d().put("SLicense", ""), e && $(p.getPanelString("Typora is now deactivated"), p.getPanelString(e)), ae()
}
```

涛之雨 发表于 2022-2-21 19:23

刚刚瞧了一下,typora.io被g?w屏蔽了。。。可惜

萌新与小白 发表于 2022-2-21 20:35

涛之雨 发表于 2022-2-21 19:23
刚刚瞧了一下,typora.io被g?w屏蔽了。。。可惜

它刚一收费就被屏蔽了,原因不知道,然后在国际官网中加了国内官网的链接

timelessxp 发表于 2022-2-21 22:36

感谢楼主的分享

涛之雨 发表于 2022-2-23 07:28

本帖最后由 涛之雨 于 2022-2-23 07:30 编辑

刚刚又跟了一下离线激活的代码,验证甚至更少。。。
只要rsa公钥可以解开,并且指纹和生存的机器码的一部分一样,并且用户名,密钥和类型不为空就行。。。。
所以理论上可以搞布丁一键解包,修改公钥成自己的,再替换,就可以写注册机了(g?w还帮我们把那个验证服务器给屏蔽了)

CRoot 发表于 2022-2-23 19:30

涛之雨 发表于 2022-2-23 07:28
刚刚又跟了一下离线激活的代码,验证甚至更少。。。
只要rsa公钥可以解开,并且指纹和生存的机器码的一部 ...

对 离线的蛮简单的,但是一直在更新从1.1.2开始分析,几天就升级到1.1.5,打算等他稳定了之后在仔细跟跟。感觉单靠fw不太行,他在网不通的时候会尝试切换国内服务器,可以把域名替换掉。

涛之雨 发表于 2022-2-23 19:35

CRoot 发表于 2022-2-23 19:30
对 离线的蛮简单的,但是一直在更新从1.1.2开始分析,几天就升级到1.1.5,打算等他稳定了之后在仔细跟跟 ...

那就再替换的时候多一步,
正则表达式把这两个域名全都replace掉
{:301_986:}
此外他这个保存的时候会自动url编码,关掉再打开的时候不会解码回来显示{:301_1001:}
(算不算bug?去提一下issue给他们说一下{:301_1001:})

CRoot 发表于 2022-2-23 19:46

涛之雨 发表于 2022-2-23 19:35
那就再替换的时候多一步,
正则表达式把这两个域名全都replace掉



{:301_1009:} 这-- 还是不要吧,会被挨打。另做好对应版本的atom.js就行,然后放到node_modules,会优先就近加载。论坛那位最早搞aes密钥的github上的issue上提到过这一点。自动化的话就用github的action全自动拉去对应版本替换发布就行。

CRoot 发表于 2022-2-26 17:49

涛之雨 发表于 2022-2-23 19:35
那就再替换的时候多一步,
正则表达式把这两个域名全都replace掉



ee = function() {
    var e = Object(s.a)(l.a.mark((function e(t) {
      var n, a, r, i, c, o, s, u, m, f, p;
      return l.a.wrap((function(e) {
            for (;;) switch (e.prev = e.next) {
                case 0:
                  if ("+" == t || "#" == t) {
                        e.next = 2;
                        break
                  }
                  return e.abrupt("return");
                case 2:
                  t = t.substr(1, t.length - 2), e.prev = 3, window.webkit && (n = t.split("|") || ["", ""], a = Object(d.a)(n, 2), r = a, i = a, (c = JSON.parse(window.atob(r))).sig = i, t = JSON.stringify(c)), e.next = 11;
                  break;
                case 7:
                  return e.prev = 7, e.t0 = e.catch(3), window.alert("Invalid Activation Token"), e.abrupt("return");
                case 11:
                  return U(!0), e.next = 14, window.Setting.invokeWithCallback("offlineActivation", t);
                case 14:
                  o = e.sent, s = Object(d.a)(o, 4), u = s, m = s, f = s, p = s, U(!1), u ? (q(m), j(!0), b(0), C(f), H(p), R("off")) : (window.alert("Invalid Activation Token"), q(m || "Unknown Error"));
                case 22:
                case "end":
                  return e.stop()
            }
      }), e, null, [
            
      ])
    })));
    return function(t) {
      return e.apply(this, arguments)
    }
}

不知道大佬注意到这段代码了没有,离线调试了许久,才发现在页面前端还有一处判断,我说我的keygen死活生成不出来。

涛之雨 发表于 2022-2-26 19:03

CRoot 发表于 2022-2-26 17:49
ee = function() {
    var e = Object(s.a)(l.a.mark((function e(t) {
...

对最前面加一个+就行了
然后可以shift+f12调试
弹出的注册窗口要用代码有一个xxx.debug,调用就有控制台了
页: [1] 2
查看完整版本: 某MarkDown编辑器在线激活流程简易分析