吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4484|回复: 10
收起左侧

[Web逆向] 某MarkDown编辑器在线激活流程简易分析

[复制链接]
CRoot 发表于 2022-2-19 11:14

在线激活

注册码本地合法校验:

const Z = e => {
    const r = "L23456789ABCDEFGHJKMNPQRSTUVWXYZ";
    if (!/^([A-Z0-9]{6}-){3}[A-Z0-9]{6}$/.exec(e)) return !1;
    var e = e.replace(/-/g, ""),
        t = e.substr(22);
    return !e.replace(/[L23456789ABCDEFGHJKMNPQRSTUVWXYZ]/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[n + i]);
            o %= r.length, t += r[o]
        }
        return t
    })(e)
}

使用网传代码轻松过:

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[i + s]);
            t += $chars[a %= $chars.length]
        }
        return t
    })(serial)
    return serial.slice(0, 6) + "-" + serial.slice(6, 12) + "-" + serial.slice(12, 18) + "-" + serial.slice(18, 24);
}

然后发送给远端服务器 api/client/activate 提交激活信息

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                             //
};

获取机器名以及当前用户名信息:

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"
    }
}

机器码获取:

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("[License] Failed to get fingerPrint"), i = T(i, "typora").substr(0, 10).replace(/[/=+-]/g, "a"), b && (i += "darwin")
    }
    return i
};

然后会根据返回值进行判断

if (JSON.stringify(o.data), console.log("[License] 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的时候,会写入注册表,保存注册信息

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("[License] validate server return fail"), V(), !1)
    } catch (e) {
        throw console.error(e.stack), new Error("WriteActivationInfoFail")
    }
}

这里的H是本地稍加检测授权,并刷新当前开启的授权状态(有一定猜测在里面),而l则是当前全局的激活状态

function H(e, t, n) {
    c = t, S = n, (l = !(!(x = e) || !c)) && re()
}

紧接着就将注册信息利用d()写入注册表了

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()[e] = t, n.write()
        }, get: function (e) {
            return n.getState()[e]
        }
    }) : O
};

同时这里要注意返回的json数据经过了I()进行处理

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算法,对返回的授权信息进行解密。

另外,启动的时候有授权合法检测,不合法仍然会掉授权。

const j = e => {
    console.log("[License] firstValidateLicense"), L = !0;
    var t = W(),
        {
            license: n,
            email: o,
            type: i
        } = t || {};
    n && o ? (H(o, n, i), B(t, e), console.log("[License] pass validateLicenseInfoStr")) : V()
}

里面调用V()函数,一般用作授权删除或者清理非法授权使用。相较于最初的验证版本,个人感觉在线验证更加复杂了一些,不过新增了离线激活模式。

function V(e) {
    l || (e = ""), c = x = "", l = !1, d().put("SLicense", ""), e && $(p.getPanelString("Typora is now deactivated"), p.getPanelString(e)), ae()
}

免费评分

参与人数 3威望 +1 吾爱币 +22 热心值 +2 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
JonesDean + 1 + 1 热心回复!
seaneo + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

涛之雨 发表于 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不太行,他在网不通的时候会尝试切换国内服务器,可以把域名替换掉。

点评

那就再替换的时候多一步, 正则表达式把这两个域名全都replace掉 此外他这个保存的时候会自动url编码,关掉再打开的时候不会解码回来显示 (算不算bug?去提一下issue给他们说一下{:301  详情 回复 发表于 2022-2-23 19:35
涛之雨 发表于 2022-2-23 19:35
CRoot 发表于 2022-2-23 19:30
对 离线的蛮简单的,但是一直在更新从1.1.2开始分析,几天就升级到1.1.5,打算等他稳定了之后在仔细跟跟 ...

那就再替换的时候多一步,
正则表达式把这两个域名全都replace掉

此外他这个保存的时候会自动url编码,关掉再打开的时候不会解码回来显示
(算不算bug?去提一下issue给他们说一下
 楼主| CRoot 发表于 2022-2-23 19:46
涛之雨 发表于 2022-2-23 19:35
那就再替换的时候多一步,
正则表达式把这两个域名全都replace掉

这-- 还是不要吧,会被挨打。另做好对应版本的atom.js就行,然后放到node_modules,会优先就近加载。论坛那位最早搞aes密钥的github上的issue上提到过这一点。自动化的话就用github的action全自动拉去对应版本替换发布就行。
 楼主| CRoot 发表于 2022-2-26 17:49
涛之雨 发表于 2022-2-23 19:35
那就再替换的时候多一步,
正则表达式把这两个域名全都replace掉

[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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[0] || "#" == t[t.length - 1]) {
                        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[0], i = a[1], (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[0], m = s[1], f = s[2], p = s[3], 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, [
            [3, 7]
        ])
    })));
    return function(t) {
        return e.apply(this, arguments)
    }
}


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

点评

对最前面加一个+就行了 然后可以shift+f12调试 弹出的注册窗口要用代码有一个xxx.debug,调用就有控制台了  详情 回复 发表于 2022-2-26 19:03
涛之雨 发表于 2022-2-26 19:03
CRoot 发表于 2022-2-26 17:49
[mw_shl_code=javascript,true]ee = function() {
    var e = Object(s.a)(l.a.mark((function e(t) {
...

对最前面加一个+就行了
然后可以shift+f12调试
弹出的注册窗口要用代码有一个xxx.debug,调用就有控制台了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-17 16:22

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表