吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 11419|回复: 135
收起左侧

[原创] 某收费 Markdown 编辑器的破解分析

    [复制链接]
大喜 发表于 2022-4-1 00:16
本帖最后由 大喜 于 2022-4-1 00:25 编辑

去年,某个 markdown 编辑器停止免费试用,开始收费了。我一直使用着免费版本,但是,突然有一天,免费版也不能用了,,,,,
其实,不激活也能使用,不过每次启动都有一个激活弹窗,就暂时用了几天,发现丢了一些笔记,也不明白什么情况,但是,,新手上路,啥都想研究一下。
先上研究成果
Screenshot 2022-04-01 001049.png

代码经过混淆,又没有经验,分析了好几天,,,,
详情看置顶帖

免费评分

参与人数 16吾爱币 +14 热心值 +16 收起 理由
ZMC0635 + 2 + 1 请问大佬,你这个说明文本代码框,是怎么弄的呢?感谢
Prince0 + 1 + 1 我很赞同!
henaliu + 1 谢谢@Thanks!
maogecarry + 1 + 1 我很赞同!
longling + 1 + 1 用心讨论,共获提升!
wda114514 + 1 + 1 谢谢@Thanks!
bestplay200 + 1 + 1 我很赞同!
Lthero + 2 + 1 挺好的
南风未起. + 1 我很赞同!
quitidle + 1 + 1 用心讨论,共获提升!
5omggx + 1 + 1 用心讨论,共获提升!
HighLightFanYa + 1 我很赞同!
EHOOD + 1 + 1 谢谢@Thanks!
allenslun + 1 + 1 谢谢@Thanks!
sam喵喵 + 1 谢谢@Thanks!看到就是一个赞
applelittle + 1 + 1 谢谢@Thanks!

查看全部评分

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

 楼主| 大喜 发表于 2022-4-1 00:25
本帖最后由 大喜 于 2022-6-16 22:16 编辑

所需文件链接在最后[捂嘴笑]

成品,,,

嗯,自己动手,丰衣足食

打开软件目录,一看这个结构就是 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

完工,睡觉,

typoraCracker.rar

955.8 KB, 下载次数: 469, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 19吾爱币 +16 热心值 +16 收起 理由
kelvar + 1 + 1 谢谢@Thanks!
Bigdangjia + 2 + 1 我很赞同!
初夏吃鸡蛋 + 1 + 1 方法现在还可以用,太赞了
0785zhao + 1 + 1 谢谢@Thanks!
celester + 1 我很赞同!
pl18gwc + 1 + 1 谢谢@Thanks!
qly + 1 谢谢@Thanks!
long8586 + 1 + 1 谢谢@Thanks!
KenG3nME + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
zyssss + 1 我很赞同!
萌新与小白 + 1 + 1 热心回复!
Courser + 1 我很赞同!
lingyun011 + 1 用心讨论,共获提升!
愷龍 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
九天临兵帝 + 1 + 1 谢谢@Thanks!
XINJIAN9 + 1 我很赞同!
xuexixiaobai + 1 + 1 谢谢@Thanks!
mormas + 1 + 1 用心讨论,共获提升!不考虑阿狸福利,给你热心和币
qing1zhe + 1 谢谢@Thanks!

查看全部评分

涛之雨 发表于 2022-4-1 19:07
本帖最后由 涛之雨 于 2022-6-15 18:05 编辑
信息 状态
🤔无效指令个数😒 Number of invalid instructions
🎉成功生成个数🎉 Numbers of successful builds
😏附件总下载量😙 GitHub all releases
🔧自动更新状态⚙️ GitHub Workflow Status
😎最新稳定版本🥳 Latest supported version
🌈最新测试版本🔬 GitHub release (release name instead of tag name)

离线注册算法更简单,我用action写了个机器人注册机

https://taozhiyu.github.io/TyProAction/ 终于没了,自动提醒我更新。。。

仪式感嘛。。。

直接使用可以用别的大佬的

别的大佬的库都没了

如果不出意外应该会继续更新,但是不提供注册机了(反正是github page一个页面)

免费评分

参与人数 13吾爱币 +14 热心值 +12 收起 理由
mmliuliuliu + 1 + 1 谢谢@Thanks!
BE一诺 + 1 + 1 谢谢@Thanks!
wunaihe + 1 + 1 谢谢@Thanks!
prodcd + 1 + 1 谢谢@Thanks!
友菱 + 1 + 1 谢谢@Thanks!
1847123212 + 1 + 1 github机器人牛
wwh0791 + 1 + 1 用心讨论,共获提升!
doggyhzj + 1 + 1 谢谢@Thanks!
cooker36 + 1 我很赞同!
The-rapist + 2 + 1 谢谢@Thanks!
XINJIAN9 + 1 我很赞同!
联盟少侠 + 2 + 1 牛逼呀大佬!膜拜了
yuanxiaoyuan277 + 1 + 1 我很赞同!

查看全部评分

AiniWang 发表于 2022-4-1 16:31
yawnwang 发表于 2022-4-1 16:34
谢谢分享
 楼主| 大喜 发表于 2022-4-1 17:18
AiniWang 发表于 2022-4-1 16:31
感谢分享,最新版支持poj吗

大意了,在我开始搞得时候,这个还是最新版,我不太更新软件
Lemon1900 发表于 2022-4-1 19:59
还得是大佬,前几天用github上的解压不出来license.js
xiaoyouy 发表于 2022-4-1 19:59
涛之雨 发表于 2022-4-1 19:07
离线注册更简单
我用action写了个机器人注册机
https://github.com/taozhiyu/TyProAction

感谢分享,6666
applelittle 发表于 2022-4-1 21:57
先存着了,感谢~
studywin 发表于 2022-4-1 22:51
谢谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-23 20:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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