所需文件链接在最后[捂嘴笑]
成品,,,
嗯,自己动手,丰衣足食
打开软件目录,一看这个结构就是 electron ,app.asar ,研究如何解包的时候,很不巧,github 有一个 patch typora 的项目,写好了解包的脚本,拿来主义,直接用好了。
但是,发现这个项目,并不发布补丁,也不对破解提供支持。还是得自己动手。
使用该脚本将文件解包,得到 atom.js ,我们将修改这个文件,达到 patch 的目的。
代码经过压缩,分析过程较为冗长,就不细说。但是,作为保姆级教程,每一步操作,我都写出来。#
1. 解压 app.asar
使用 github 的脚本,地址https://github.com/Mas0nShi/typoraCracker
pip install -r requirments.txt # 安装依赖
python typora.py -f [安装目录,app.asar 所在路径] ./out
]然后,你会在当前目录得到一个 out/dec_app 文件夹,里面有 atom.js
. patch
先进行字符串搜索,进行校验的地方,在源码中搜索 activate 字符串,搜索到 7 处,其他 5 处 都是字符串常量,其中两处较为明显,属于校验逻辑, 分别是激活和取消激活,其中一处代码如下
根据此处代码上下文中的字符串很容易确定,此处属于 校验逻辑,看到 await R("api/client/activate", t, !0); 一个异步函数,感觉像是一个封装的网络请求,跟进代码看一下,找到函数实现
R = async (t, n, o) => {
ee(), console.log(`request ${E}/` + t);
const i = a(225).post;
try {
var r = await i(E + "/" + t, n, {
timeout: 3e4,
headers: {
"Cache-Control": "no-cache"
}
});
return r
} catch (e) {
if (console.warn(e.stack), e.response) throw e;
if (o && "zh-Hans" == s.setting.getUserLocale() && !s.setting.get("useMirrorInCN")) {
o = (await h.dialog.showMessageBox(null, {
message: "链接服务器失败,使用尝试访问国内域名进行激活?",
buttons: ["确认", "取消"]
}))["response"];
if (console.log("click " + o), o) throw e;
return s.setting.put("useMirrorInCN", !0), R(t, n, !1)
}
if (!s.setting.get("useMirrorInCN")) throw e;
o = e;
try {
console.log("request to typora.com.cn"), r = await i("https://typora.com.cn/store/" + t, n)
} catch (e) {
throw console.warn(e.stack), o
}
}
}
十分明显,这就是一个网络请求校验激活的过程
所以,修改逻辑很简单咯,直接拦截这个网络请求,返回一个假数据咯。(闲话)
o 是返回数据,只要伪造一个 o 就好了。
(本来不知道,伪造的数据是什么,之前浏览代码的时候,发现一串密钥,想必是非对称加密的数据,于是,想摸清楚请求数据之后,在研究数据解密,忙就放了几天)但是,后来,发现 github 仓库,有人上传了一份 license.js ,本着,先用上再说的精神,我直接把代码拿来观摩,好家伙,难度直线下降。
以下数据结构来自 license.js 不过,它是 1.02 版本的,我的是 1.1.5 版本,里面需要多加一个 type 参数(不过,其实影响不大,后面写注册表的时候,可以直接删除校验代码,实际上,date 也很多余);
主要修改以下地方
// “var o = await R("api/client/activate", t, !0);”
// 替换为
const o = {
data: {
code: 0,
msg: Buffer.from(JSON.stringify({
deviceId: t.u,
fingerprint: t.f,
email: t.email,
license: t.license,
version: t.v,
type: t.type,
date: (new Date).toLocaleDateString("en-US"),
}), "utf-8").toString("base64")
}
};
~~将代码打包运行测试,发现,提示 许可证不可用
这玩意,我也没法动态调试,于是,自己加了个土断点,,,,直接加一个文件写出,,,,,~~
经过多次尝试,确认问题出在
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"];
中的 Y()函数,
这句代码操作是,检验服务器返回的状态码,如果成功,则执行 返回Y(o.data.msg),否则,返回许可证非法
看一下 Y() 的函数实现
async 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")
}
}
4月1日,凌晨,昨天睡晚了,累得慌,明日在写吧
4.1 睡醒了
这段代码是将传入参数e进行解码,然后解析参数,如果参数齐全,则将注册信息写入注册表。
开始的时候,写注册表一直失败,后来,意识到,传入参数 e 是 base64 编码过的,此处 e 经过函数 I() 解码
(因为传入参数,是来自 license.js 所以,忘了这茬,写半天,重启之后,授权消失)开始以为是注册表写出错了,检查写注册表的函数 ,就是那个 H()很久,后来,发现没有问题。
看一下 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
}
},
T = function() {
var e = Array.from(arguments);
const t = a(289).createHash("sha256");
return e.forEach(e => {
t.update(e)
}), t.digest("base64")
},
W = () => {
const e = d().get("SLicense");
if (!e) return null;
var [t, n, o] = e.split("#"), t = I(t);
return t && t.fingerprint == i ? (Object.assign(t, {
failCounts: n,
lastRetry: new Date(o)
}), t) : null
},
_ = async e => {
console.log("writeInstallDate fromBTime=" + e);
var t = new Date;
if (e) try {
var n = await a(728).stat(s.getPath("userData") + "/profile.data"),
t = new Date(n.birthtime);
n.birthtime
} catch (e) {}
e = (u = t).toLocaleDateString("en-US");
return d().put("IDate", e), u
};
此处验证了开始的猜想,返回的数据,经过一次非对称加密(私钥加密,公钥解密?emmmm,看上去像签名,但是,他确实是解密),收到返回消息以后,此处需进行一次解密,但是但,可是可 我们刚才写返回数据的时候,直接写了一串base64编码,所以,此处使用公钥publicDecrypt出来的数据,是错的。
很简单,直接修改
这些 过程通通不要,在函数开头,直接返回就好了,将I()的函数实现改成这样
const I = e => {
if (!e) return e;
return JSON.parse(Buffer(e, 'base64').toString());
// 后面的代码,可以全部不要了。
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
}
},
T = function() {
var e = Array.from(arguments);
const t = a(289).createHash("sha256");
return e.forEach(e => {
t.update(e)
}), t.digest("base64")
},
W = () => {
const e = d().get("SLicense");
if (!e) return null;
var [t, n, o] = e.split("#"), t = I(t);
return t && t.fingerprint == i ? (Object.assign(t, {
failCounts: n,
lastRetry: new Date(o)
}), t) : null
},
_ = async e => {
console.log("writeInstallDate fromBTime=" + e);
var t = new Date;
if (e) try {
var n = await a(728).stat(s.getPath("userData") + "/profile.data"),
t = new Date(n.birthtime);
n.birthtime
} catch (e) {}
e = (u = t).toLocaleDateString("en-US");
return d().put("IDate", e), u
};
此时,就可以发现,随便填一个邮箱就注册成功了,但是问题来了,在重启软件之后,注册状态就消失了,需要重新激活。
开始以为是每次启动软件时,都会重新联网校验激活状态,想必,联网需要使用那个封装的 请求 函数,也需要 URL,所以在代码里搜索了一阵,但是,只发现两处,网络请求的操作,一处是激活,一处是取消激活。转而去查看注册表,发现一个问题,在注册以后,注册表成功写入了注册信息,但是,在重启软件以后,刷新注册表,发现注册表被覆盖了。
不管是什么原因,如果能阻止他覆盖注册表......嗯...
在开始的查找中,找到一处取消激活的代码,实现如下
function V(e) {
l || (e = ""), c = x = "", l = !1, d().put("SLicense", ""), e && $(p.getPanelString("Typora is now deactivated"), p.getPanelString(e)), ae()
}
直接清空,但是,发现,并不起作用。
我想,在注册过程中,注册日期始终是 new date,但是,如果他判断许可日期的方式,很可能不是注册日期加一个固定的时间,那么这个日期会在哪里写入呢,在注册表,看到写入的信息结构是这样的
licenses # date
于是尝试手动修改注册表日期,发现,重启后,激活状态仍在,到此,算是全部结束了
大意了,在刚才写注册表的地方,就是那个 Y()函数,修改代码如下
async 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(2055,1,1)).toLocaleDateString("en-US")), l = !0) : (console.log("[License] validate server return fail"), V(), !1)
} catch (e) {
throw console.error(e.stack), new Error("WriteActivationInfoFail")
}
}
到此,patch 全部完成,将代码重新打包,复制回去,完美
总结一下
解包
pip install -r requirments.txt # 安装依赖
python typora.py -f [安装目录,app.asar 所在路径] ./out
篡改请求
// “var o = await R("api/client/activate", t, !0);”
// 替换为
const o = {
data: {
code: 0,
msg: Buffer.from(JSON.stringify({
deviceId: t.u,
fingerprint: t.f,
email: t.email,
license: t.license,
version: t.v,
type: t.type,
date: (new Date).toLocaleDateString("en-US"),
}), "utf-8").toString("base64")
}
};
修改解密函数
const I = e => {
if (!e) return e;
return JSON.parse(Buffer(e, 'base64').toString());
};
修改写注册表时的日期
async 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")
}
}
修改为
async 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(2055,1,1)).toLocaleDateString("en-US")), l = !0) : (console.log("[License] validate server return fail"), V(), !1)
} catch (e) {
throw console.error(e.stack), new Error("WriteActivationInfoFail")
}
}
修改取消激活时的联网请求
直接搜索注释一下代码
await R("api/client/deactivate", {
license: c,
l: e,
sig: T(x, await M(), c)
}, !1)
打包回去
python typora.py -f /out/dec_app/ ./pkg
把 pkg 目录下的 app.asar 复制到安装目录下,再次打开,随意输入邮箱和注册码,注册成功!
完美
你们想要的东西在这里
2510241
没有 CB ?
阿里云盘连接
直接点击运行安装即可。
https://www.aliyundrive.com/s/EhhrnXg3f3U
看帖回帖是一种美德
typora 1.1.5 Windows x64 安装包:
https://download.typora.io/windows/typora-setup-x64-1.1.5.exe
我今天一看,上次打包的成品可能有误,,,,,所以我直接再补发一个吧
~~链接:https://pan.baidu.com/s/1bTVdRXWjozyKkKVRzZpw1A ~~
~~提取码:626w ~~
--来自百度网盘超级会员V3的分享
解压密码 j1sdaxi
使用激活码激活:JTVKNC-NQZRJN-GWR7SM-PH9SBT
完工,睡觉,