吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4144|回复: 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] 纯文本查看 复制代码
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, 2024-12-25 02:32

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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