MindBox(Neatify笔记)简单PJ
MindBox(Neatify笔记 v2.0.0)简单破解
1.前言
本人小白, 而且在此之前未曾使用过此软件, 在果核上偶然看见了此软件的更新, 详情请查看果核推荐页面, 然后评论区有提到是否有修改版, 然后就下载软件, 开始了逆向之路
1.1 软件下载&安装
从官网下载软件, 软件100多M, 猜想是Electron实现
使用解压软件打开安装包, 果不其然, 证实了猜想
对于这类(Electron)软件, 我个人基本都是直接解压app-64.7z这个压缩文件到对应的目录, 省去安装的步骤, 但缺点就是没法关联打开文件
解压后得到如下结构
此时先不急打开, 因为此类(Electron)软件, 直接打开exe, 通常会在C盘建立一个目录, 并存放一些缓存和配置
如果不喜欢的话, 可以将Mindbox.exe创建一个快捷方式, 并添加参数--user-data-dir=D:\PortablePrograms\Mindbox\data
, 这样就能将C盘的缓存目录重定向到user-data-dir
这个目录上
但一旦直接打开了exe, 就会在c盘创建目录
1.2.打开软件
软件的基础版已经完全够用, 诚意还是够的, 但作为一名论坛混子, 虽然能力不行, 但想法还是要有的
2.常规分析
2.1 解包
对于此类软件, 代码基本都在resources/app.asar
里头, asar可以看成一个压缩包, 但和常规的压缩类型不同, 所以不能以压缩软件打开, 需要安装asar模块进行解压和打包
// 安装asar
npm install -g asar
// 解压app.asar, 解压到app就可以直接运行程序, 不必重新打包再测试
asar extract app.asar ./app
至此, 在resources/app下边就有解压出来的资源文件
将原有的app.asar备份删除, 或者修改为别的名字
2.2 分析
用WebStorm打开app目录
vscode也行, 但webstorm格式化效果比较好
回到软件界面上, 点击标题栏上的皇冠图标, 第一次打开是登录框, 注册账号并登录后, 会弹出订阅套餐界面
这就有两个方向, 一个是处理购买订阅后的结果, 第二个是使用下方的恢复订阅
emmm 此处选择第二种方式
点击按钮后会出现恢复购买失败的提示
在WebStorm中搜索 恢复购买失败
, 可以发现只有两个地方出现了, 而且并不是出现在js逻辑代码里头, 他们对的英文属性名也是一致的resumePurchaseFailed
因此转而搜索resumePurchaseFailed
, 可以看到多出了一个搜索结果
那这个结果所在的函数, 就是我们重点要关注的地方, 将函数单独复制出来, 并格式化看看大致逻辑
查找到的js文件可以使用ctrl+alt+l格式化
async function getProFromFocusNote(G = !1) {
// 定义U=恢复购买失败提示信息
let U = getI18nText("resumePurchaseFailed");
// 检查Pro许可
const W = await checkProTransfer();
// if判断可直接删除
if (W) {
// emmm..., 虽然不清楚是干什么的,但我们可以直接暴力跳过判断
const K = await ProMigrationModal(G);
// if判断可直接删除
if (K === 0) {
// 更新用户信息
const X = await refreshUserInfo();
// 设置用户信息
window.electron.ipcRenderer.invoke("setUserInfo", X),
U = getI18nText("resumePurchaseSuccess")
} K !== -1 && Message({ message: U })
}
else G && Message({ message: U });
return W
}
大致逻辑就是通过一些判断, 要么提示恢复购买失败, 要么更新用户信息, 并提示恢复购买成功, 所以这些判断都可以直接去掉
async function getProFromFocusNote(G = !1) {
// 更新用户信息
const X = await refreshUserInfo();
// 设置用户信息
window.electron.ipcRenderer.invoke("setUserInfo", X);
return 1
}
那么关键函数就是这个refreshUserInfo
, 只要它能得到一个包含PRO许可的结果, 软件就会刷新用户信息
async function refreshUserInfo() {
// 获取用户token
const G = await requestApi({ type: "get", url: "/auth/account/refresh-token" });
if (G.success) {
// 获取用户购买的产品清单
const U = await getUserProductList({ headers: { [G.data.tokenHead]: G.data.token } });
return {
...G.data,
// 这个U就是关键, 要如何构造出U的报文结构
purchaseList: U
}
}
}
查看getUserProductList
函数
async function getUserProductList({headers: G} = {}) {
let U = {
type: "get",
url: "/auth/user-product/list",
// 查询用户购买的产品所使用的请求参数
// appId应为此软件的ID, productType即产品版本, showExpired是否显示过期(订阅)产品
params: {appId: 5, productType: "PRO", showExpired: !1},
headers: {"content-type": "application/x-www-form-urlencoded"}
};
if (G) for (let K in G) U.headers[K] = G[K];
const W = await requestApi(U);
return W.success ? W.data : []
}
根据上述信息, 即可尝试修改返回结果, 使其包含productType: "PRO"
因此直接将此函数改为如下内容
async function getUserProductList({headers: G} = {}) {
return [{productType: "PRO"}]
}
注意, 需把 getProFromFocusNote相关判断去掉, 否则程序到不了refreshUserInfo这一步, 也就到不了getUserProductList
保存后重新启动程序, 并点击 恢复购买, 左上角查看账户信息, 已经更新为PRO, 但日期是失效的
到目前为止, 软件打开已经显示为PRO版本, 但重新打开可能会变为基础版, 需要重新点击恢复购买
2.3 修复
方法和上边类似, 仍然按照关键字搜索, 在IDE中搜索您的订阅时长有效期至
再搜索英文属性pro_duration
, 即可找到关键位置
代码格式化后查看更好分析
关键代码
// 字段定义, 在u函数上方
const {t} = q(), r = re(), c = se(), i = E(() => c.userInfo), d = E(() => r.useSync), h = E(() => c.isPro), k = Xe();
async function u() {
// i.value是用户信息, h.value是c.isPro
// 在IDE中可以按下ctrl键+鼠标左键,进入属性或方法, 比如此处的i和h
if (i.value) if (h.value) {
// 循环用户信息中的purchaseList, 过滤出productType = "PRO"的数据
// 此处也找到了另一个关键属性: expireTime, 这个字段就表示过期时间
const $ = Math.max(...i.value.purchaseList.filter(S => S.productType === "PRO").map(S => S.expireTime)),
// 如果超过50年即为永久
x = ge($).diff(ge(), "year") > 50;
await ee({
// 产品类型
title: t("proType"),
// 过期时间
message: t("pro_duration") + (x ? t("permanent") : ge($).format("YYYY/M/D")) + "。",
// 取消按钮
cancelText: t("cancel"),
// 续费按钮
okText: t("renew")
}) && C()
} else C()
}
综上所述, 只需要在getUserProductList
函数中增加expireTime
即可, 值类型为时间戳
async function getUserProductList({headers: G} = {}) {
// 2099-08-04 01:51:24
return [{productType: "PRO", expireTime: 4089462684000}]
}
注意: 如果重新打开界面不是PRO版本, 点击恢复购买即可, 或者在js中主动调用一次getProFromFocusNote(1);
, 这样每次启动都会去刷新用户的信息
2.4 打包
修改上述文件均在app目录下操作js, Electron默认会加载app目录下的文件, 所以打包也不是必要的
// 打包命令
asar pack ./ app.asar
// 移动到上一层
move app.asar ../
2.5大功告成
3.更简单的方法
在上述分析过程中, 我们找到了关键的productType
和expireTime
的处理逻辑, 而进入这个逻辑的判断条件有两个
const {t} = q(), r = re(), c = se(), i = E(() => c.userInfo), d = E(() => r.useSync), h = E(() => c.isPro), k = Xe();
async function u() {
// i.value是用户信息, h.value是c.isPro
// 在IDE中可以按下ctrl键+鼠标左键,进入属性或方法, 比如此处的i和h
if (i.value) if (h.value) {}
}
这里我们直接查找h的定义(按Ctrl+鼠标左键), 可以发现 h = c.isPro, c = se()
, 我们再进入se方法, 可以发现如下定义
// se()
function useUserStore() {
return useStore$2(store)
}
const store = createPinia(), useStore$2 = defineStore({
id: "user",
state: () => ({userInfo: null, receiptList: []}),
actions: {
async getUserInfo() {
this.userInfo = await window.electron.ipcRenderer.invoke("getUserInfo")
}, async getReceiptList() {
this.receiptList = await window.electron.ipcRenderer.invoke("getAppData", {key: "receiptList", orNot: []})
}
},
// isPro就是根据用户的购买的清单来获取的
getters: {isPro: G => G.userInfo && G.userInfo.purchaseList ? G.userInfo.purchaseList.findIndex(U => U.productType === "PRO") !== -1 : !!(G.receiptList.length > 0 && checkReceiptValid(G.receiptList))}
});
到这里就能找打isPro的定义, 既然这样, 何不那样, 对吧 :)
{
...
// 直接返回1
getters: {isPro:G=>1}
}
所以更简的方式就是, 解压app.asar后, 直接找到函数useStore$2
的定义, 修改函数G
的返回结果, 这样哪怕不登陆, 也能用PRO的功能
当然这样日期会显示无效日期, 强迫症可以根据前边的分修改日期的显示
// 过期时间显示定义: 您的订阅时长有效期至:+ 永久有效/具体到期日期 + 。
message: t("pro_duration") + (x ? t("permanent") : ge($).format("YYYY/M/D")) + "。"
// 如修改为永久, 并加上相应提示
message: t("pro_duration") + t("permanent") + " by 52pj。"
4.总结
直接修改文件 app/dist/electron/renderer/assets/index-yqoE38iR.js方法 即可破解
async function getUserProductList({headers: G} = {}) {
return [{productType: "PRO"}]
}
- 重新梳理一遍分析流程, 对整个流程有了更多的认识, 也找到了更简洁的方法
- 软件的这个版本还没有复杂的加密, 解密之类的处理, 可以用于练练手
- 开发不易, 希望大家尊重软件开发团队的成果, 不要传播破解版本, 以免引起不必要的纠纷
本项目只做个人学习研究之用,不得用于商业用途!
若资金允许,请点击链接购买正版,谢谢合作
版权声明:
作者:超级管理员
链接:
https://apecloud.ltd/article/detail.html?id=183
来源:猿码云个人技术站
文章版权归作者所有,未经允许请勿转载。
共有0条评论