Typora 分析记录

在线激活

注册码本地合法校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
}

使用网传代码轻松过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 提交激活信息

1
2
3
4
5
6
7
8
9
10
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 //
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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"
}
}

机器码获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
};

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

1
2
3
4
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的时候,会写入注册表,保存注册信息

1
2
3
4
5
6
7
8
9
10
11
12
13
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则是当前全局的激活状态

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()进行处理

1
2
3
4
5
6
7
8
9
10
11
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算法,对返回的授权信息进行解密。

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

1
2
3
4
5
6
7
8
9
10
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()函数,一般用作授权删除或者清理非法授权使用。相较于最初的验证版本,个人感觉验证更加复杂了一些,对于patch点也需要更多,不过新增了离线激活模式,不知道这个模式下的激活是不是更加容易了呢。

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

下一步,如果有时间可能会看看离线算法,如果没时间就又烂尾了–