使用范围
理论可使用版本是23.07以下的23版本。
安装XMind
官网下载安装即可,或下载此链接:Download XMind 23.07
依赖
需要npm
安装asar
:
npm install -g asar
需求的三个文件:
破解过程
1. 解压
进入到路径%USERPROFILE%/AppData/Local/Programs/Xmind/resources
,使用asar
解压:
asar extract .\app.asar ./dist/
2. 替换
使用VSCode打开dist文件夹,全局搜索:
String.fromCharCode(45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,71,102,77,65,48,71,67,83,113,71,83,73,98,51,68,81,69,66,65,81,85,65,65,52,71,78,65,68,67,66,105,81,75,66,103,81,67,68,89,72,51,49,108,48,108,108,105,99,66,97,118,98,85,90,82,103,48,121,49,76,110,73,10,50,74,74,117,80,90,97,107,48,52,57,56,119,71,109,75,48,78,43,107,115,113,67,122,65,48,88,85,102,67,103,81,53,69,57,105,116,89,121,80,117,84,43,122,54,80,122,47,43,48,113,54,78,101,65,112,107,87,99,110,67,47,84,104,10,87,81,89,54,90,108,69,79,77,111,110,114,104,80,117,98,56,122,115,87,89,79,90,122,99,107,81,117,116,120,51,106,110,54,107,43,54,90,88,120,55,121,85,98,98,107,120,73,107,43,119,113,87,103,110,108,81,120,110,120,54,84,77,100,10,83,51,114,103,111,51,114,52,98,108,70,84,87,105,54,69,69,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)
将其替换为:
String.fromCharCode(45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,113,73,71,102,103,104,120,115,47,115,99,104,106,105,100,43,109,72,108,75,10,65,81,104,87,72,109,49,122,49,117,80,88,47,67,87,114,50,84,66,72,80,99,103,51,80,68,109,70,56,118,86,105,121,104,117,120,112,107,86,101,52,47,88,52,84,122,57,104,78,57,66,71,65,43,104,55,116,111,72,85,119,54,114,75,10,122,50,77,107,53,77,53,112,101,71,53,73,100,52,68,86,76,65,68,117,86,100,112,99,98,106,111,48,89,112,99,48,109,79,100,68,68,84,74,116,108,99,50,84,56,113,49,48,114,100,71,89,68,48,69,114,112,101,82,57,83,117,57,105,10,97,74,120,68,87,77,79,76,108,78,122,112,109,87,88,112,103,75,81,87,106,82,117,122,111,73,114,79,105,105,72,118,71,122,65,105,83,114,67,77,75,116,54,109,43,47,109,43,83,118,114,53,67,81,72,119,43,47,74,120,49,105,65,119,10,121,77,90,73,77,119,117,120,56,103,115,103,97,119,86,116,85,49,117,54,77,109,73,66,57,112,120,52,74,110,99,70,101,112,115,103,51,70,100,83,69,98,113,100,89,90,76,51,77,101,69,120,68,84,55,80,80,104,50,71,81,99,98,83,10,102,99,108,49,103,89,84,114,67,103,74,70,85,90,85,114,50,74,66,79,83,86,73,111,73,118,71,65,84,72,55,86,73,77,89,66,87,97,110,116,98,65,105,81,103,71,113,107,74,115,116,88,98,56,85,110,103,69,77,52,104,114,115,88,10,117,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)
3. 本地Hook服务
上面提供的三个附件,hook.js
放入到main
目录中,然后在main
目录中创建hook
目录,将crypto.js
和electron.js
放到dist/main/hook
目录下,并且在dist/main/main.js
的开头添加:
require('./hook')
4. 重新打包
可以先将原本的
app.asar
重命名为app.asar.bak
做备份,防止翻车
执行命令:
asar pack ./dist/ app.asar
使用登录
使用任意账号密码登录即可,由于存在本地Hook服务,都会认证通过。
Hook服务使用了本地的3000端口,如果该端口被占用,会导致认证出错。
原理探究
实际上是通过劫持的相关的API
,使得请求直接走向了本地的Hook服务,然后返回指定内容,进而使得软件认为授权成功。
明白原理后开始着手写一下破解脚本简化破解过程,并且对这个破解方式做一下优化。
随机端口优化
不妨定义以下函数:
function getAvailablePort() {
const net = require('net')
return new Promise(res => {
const srv = net.createServer()
srv.listen(0, () => {
const port = srv.address().port
srv.close(() => res(port))
})
})
}
listen(0, () => {})
就可以获取到一个随机的且未被占用的端口号进行监听,随后只需要获取其端口号就可以取到一个随机的、可用的端口号。
注册器
接下来编写注册器,参考上面的hook.js
,其实可以将所有的东西都集成到一个文件里,这更有利于破解的便捷性,注册器代码不在此贴出,本质思路就是将所有文件的内容合并,然后通过修改随机端口监听使得在本地3000端口被占用后也可以正常启动软件。可以通过此链接跳转到注册器代码位置:XMind破解#注册器代码
破解脚本
首先给出常量定义:
const path = `${process.env.HOME||process.env.USERPROFILE}/AppData/Local/Programs/Xmind/resources`
const extratFolder = 'dist'
参照上面的过程,首先进行的是解压app.asar
:
function extractCode() {
if (!fs.existsSync(path)) {
console.error('[!] Error: Could not found XMind Installation.')
exit(-1)
}
console.log('[*] Info : Start extracting...')
asar.extractAll(`${path}/app.asar`, `${path}/${extratFolder}/`)
if (!(fs.existsSync(`${path}/${extratFolder}/main`) && fs.existsSync(`${path}/${extratFolder}/renderer`) && fs.existsSync(`${path}/${extratFolder}/static`) && fs.existsSync(`${path}/${extratFolder}/package.json`))){
console.error('[!] Error: Extract error')
exit(-1)
}
console.log('[*] Info : Extract finish')
}
这里简单判断了解压后的文件结构来判断是否解压成功。
随后是替换证书公钥:
function replaceCertificatePublicKey() {
const searchString = Buffer.from('String.fromCharCode(45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,71,102,77,65,48,71,67,83,113,71,83,73,98,51,68,81,69,66,65,81,85,65,65,52,71,78,65,68,67,66,105,81,75,66,103,81,67,68,89,72,51,49,108,48,108,108,105,99,66,97,118,98,85,90,82,103,48,121,49,76,110,73,10,50,74,74,117,80,90,97,107,48,52,57,56,119,71,109,75,48,78,43,107,115,113,67,122,65,48,88,85,102,67,103,81,53,69,57,105,116,89,121,80,117,84,43,122,54,80,122,47,43,48,113,54,78,101,65,112,107,87,99,110,67,47,84,104,10,87,81,89,54,90,108,69,79,77,111,110,114,104,80,117,98,56,122,115,87,89,79,90,122,99,107,81,117,116,120,51,106,110,54,107,43,54,90,88,120,55,121,85,98,98,107,120,73,107,43,119,113,87,103,110,108,81,120,110,120,54,84,77,100,10,83,51,114,103,111,51,114,52,98,108,70,84,87,105,54,69,69,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)')
const replaceString = Buffer.from('String.fromCharCode(45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,113,73,71,102,103,104,120,115,47,115,99,104,106,105,100,43,109,72,108,75,10,65,81,104,87,72,109,49,122,49,117,80,88,47,67,87,114,50,84,66,72,80,99,103,51,80,68,109,70,56,118,86,105,121,104,117,120,112,107,86,101,52,47,88,52,84,122,57,104,78,57,66,71,65,43,104,55,116,111,72,85,119,54,114,75,10,122,50,77,107,53,77,53,112,101,71,53,73,100,52,68,86,76,65,68,117,86,100,112,99,98,106,111,48,89,112,99,48,109,79,100,68,68,84,74,116,108,99,50,84,56,113,49,48,114,100,71,89,68,48,69,114,112,101,82,57,83,117,57,105,10,97,74,120,68,87,77,79,76,108,78,122,112,109,87,88,112,103,75,81,87,106,82,117,122,111,73,114,79,105,105,72,118,71,122,65,105,83,114,67,77,75,116,54,109,43,47,109,43,83,118,114,53,67,81,72,119,43,47,74,120,49,105,65,119,10,121,77,90,73,77,119,117,120,56,103,115,103,97,119,86,116,85,49,117,54,77,109,73,66,57,112,120,52,74,110,99,70,101,112,115,103,51,70,100,83,69,98,113,100,89,90,76,51,77,101,69,120,68,84,55,80,80,104,50,71,81,99,98,83,10,102,99,108,49,103,89,84,114,67,103,74,70,85,90,85,114,50,74,66,79,83,86,73,111,73,118,71,65,84,72,55,86,73,77,89,66,87,97,110,116,98,65,105,81,103,71,113,107,74,115,116,88,98,56,85,110,103,69,77,52,104,114,115,88,10,117,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)')
function walkDir(dir) {
let list = []
fs.readdirSync(dir).forEach((item) => {
list.push(item)
})
return list
}
const baseDir = `${path}/${extratFolder}/renderer`
let jsFile = walkDir(baseDir).filter((name) => name.endsWith('.js'))
console.log('[*] Info : Replace software authentic certificate...')
let isHited = false
for (let index = 0; index < jsFile.length; index++) {
let content = fs.readFileSync(`${baseDir}/${jsFile[index]}`)
let foundIndex = content.indexOf(searchString)
if (foundIndex != -1) {
console.log(`[*] Info : Target hit in file ${jsFile[index]}`)
const finalContent = content.subarray(0, foundIndex) + replaceString + content.subarray(foundIndex + searchString.length)
fs.writeFileSync(`${baseDir}/${jsFile[index]}`, finalContent)
console.log('[*] Info : Replace certificate finish')
isHited = true
break
}
}
if (!isHited) {
console.log('[!] Error: Target not found, exited.')
exit(-1)
}
}
接下来是写入注册器,并且在main.js
最上面添加代码,使注册器在最前面加载,等同上面的复制hook.js
相关步骤:
function writeActivator() {
console.log('[*] Info : Applying Activator...')
if (fs.existsSync(`${__dirname}/activator.js`)) {
} else {
console.log('[!] Error: Activator lost')
exit(-1)
}
fs.copyFileSync(`${__dirname}/activator.js`, `${path}/${extratFolder}/main/activator.js`)
console.log('[*] Info : Apply activator finish')
console.log('[*] Info : Register activator...')
fs.writeFileSync(`${path}/${extratFolder}/main/main.js` ,Buffer.from(`require('./activator')\n`) + fs.readFileSync(`${path}/${extratFolder}/main/main.js`))
console.log('[*] Info : Register finish')
}
最后就是重新打包为asar
:
function cleanup() {
console.log('[*] Info : Deleting code dir...')
fs.rmSync(`${path}/${extratFolder}`, {force: true, recursive: true})
if (fs.existsSync(`${path}/${extratFolder}`)) {
console.log(`[^] Warn : Cleanup failed, some file still in "${path}/${extratFolder}", delete it manually`)
} else {
console.log('[*] Info : Cleanup finish')
}
}
function repack() {
console.log('[*] Info : Back up official software')
fs.renameSync(`${path}/app.asar`, `${path}/app.asar.bak`)
console.log(`[*] Info : Your software backup is "${path}/app.asar.bak", if something wrong, rename it to app.asar`)
console.log('[*] Info : Repacking software...')
asar.createPackage(`${path}/${extratFolder}/`, `${path}/app.asar`).finally(() => {
// cleanup
cleanup()
if (fs.existsSync(`${path}/app.asar`)) {
console.log('[*] Info : Repack finish, now you could open XMind and input any account with any password to upgrade it with pro edition')
} else {
console.log('[!] Error: Repack failed, rollback...')
fs.renameSync(`${path}/app.asar.bak`, `${path}/app.asar`)
console.log('[*] Info : Rollback finish. Activation progress has failed, try it again.')
}
})
}
这样子,就完成了破解。
完整脚本可以通过这个链接跳转:XMind破解#破解脚本代码
附录
注册器代码
保存为
activator.js
const http = require("http");
const url = require("url");
function getAvailablePort() {
const net = require('net')
return new Promise(res => {
const srv = net.createServer()
srv.listen(0, () => {
const port = srv.address().port
srv.close(() => res(port))
})
})
}
const hostname = "127.0.0.1";
let server_port = 0
getAvailablePort().then(port => {
// persist port
server_port = port
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const method = req.method;
res.setHeader("Content-Type", "application/json; charset=utf-8");
if (path === "/_res/session" && method === "GET") {
res.statusCode = 200;
res.end(
JSON.stringify({
uid: "_xmind_1234567890",
group_name: "",
phone: "18888888888",
group_logo: "",
user: "_xmind_1234567890",
cloud_site: "cn",
expireDate: 4093057076000,
emailhash: "1234567890",
userid: 1234567890,
if_cxm: 0,
_code: 200,
token: "1234567890",
limit: 0,
primary_email: "crack@xmind",
fullname: "crack@xmind",
type: null,
})
);
} else if (path === "/_api/check_vana_trial" && method === "POST") {
res.statusCode = 200;
res.end(JSON.stringify({ code: 200, _code: 200 }));
} else if (path === "/_res/get-vana-price" && method === "GET") {
res.statusCode = 200;
res.end(
JSON.stringify({
products: [
{ month: 6, price: { cny: 0, usd: 0 }, type: "bundle" },
{ month: 12, price: { cny: 0, usd: 0 }, type: "bundle" },
],
code: 200,
_code: 200,
})
);
} else if (path === "/_api/events" && method === "GET") {
res.statusCode = 200;
res.end(JSON.stringify({ code: 200, _code: 200 }));
} else if (path === "/_res/user_sub_status" && method === "GET") {
res.statusCode = 200;
res.end(JSON.stringify({ _code: 200 }));
} else if (path === "/piwik.php" && method === "POST") {
res.statusCode = 200;
res.end(JSON.stringify({ code: 200, _code: 200 }));
} else if (path.startsWith("/_res/token/") && method === "POST") {
res.statusCode = 200;
res.end(
JSON.stringify({
uid: "_xmind_1234567890",
group_name: "",
phone: "18888888888",
group_logo: "",
user: "_xmind_1234567890",
cloud_site: "cn",
expireDate: 4093057076000,
emailhash: "1234567890",
userid: 1234567890,
if_cxm: 0,
_code: 200,
token: "1234567890",
limit: 0,
primary_email: "xmind@cracked",
fullname: "xmind@cracked",
type: null,
})
);
} else if (path === "/_res/devices" && method === "POST") {
res.statusCode = 200;
res.end(
JSON.stringify({
raw_data:
"TiT1ul64lE+EMrH0ogOPWHZw5r3sE+jU1l2smjmRuvxmqN3v0NPklgJI9ZpGBt3MZ/mRXM+KmmlZy/bXopy74SH7VLeg3Y1aCATUoWsY2O0XXy1I0JtvLsIF+uM6G2oOx8F6f5Wz+Embhg6b9SIF19MBckmXOOfahd0zWJDaxzpAYthagLgakhbG8k7ynXrUmGIaVmxcktxg3hnRgxlwKvJfM56x5lxF+eLY/t4EFBKfk++omYQExwflUwTrwdeP4kbQvNTMGi9v5Nmyg8Nq7w47sfc1zfeg5opDhW47JTzu29EveGXXAxgV88pjQDZMWjL5c+v4PprDSzF+KJGSfA==",
license: {
status: "sub",
expireTime: 4093057076000,
},
_code: 200,
})
);
} else {
res.statusCode = 404;
res.end("Not Found");
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
})
const crypto = require("crypto");
// save origin publicDecrypt
const originalPublicDecrypt = crypto.publicDecrypt;
/**
* Offical decrypt
* @param {string} message encrypted message
* @returns
*/
const originalPublicDecryptEx = function (message) {
let key = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDYH31l0llicBavbUZRg0y1LnI\n2JJuPZak0498wGmK0N+ksqCzA0XUfCgQ5E9itYyPuT+z6Pz/+0q6NeApkWcnC/Th\nWQY6ZlEOMonrhPub8zsWYOZzckQutx3jn6k+6ZXx7yUbbkxIk+wqWgnlQxnx6TMd\nS3rgo3r4blFTWi6EEQIDAQAB\n-----END PUBLIC KEY-----`;
const n = originalPublicDecrypt(
{
key: key,
padding: 1,
},
message
);
return n;
};
// define it as getter, return new implemention
Object.defineProperty(crypto, "publicDecrypt", {
get() {
return function myPublicDecrypt(...args) {
console.log("Activator decrypting...")
args[0]["key"] =
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIGfghxs/schjid+mHlK\nAQhWHm1z1uPX/CWr2TBHPcg3PDmF8vViyhuxpkVe4/X4Tz9hN9BGA+h7toHUw6rK\nz2Mk5M5peG5Id4DVLADuVdpcbjo0Ypc0mOdDDTJtlc2T8q10rdGYD0ErpeR9Su9i\naJxDWMOLlNzpmWXpgKQWjRuzoIrOiiHvGzAiSrCMKt6m+/m+Svr5CQHw+/Jx1iAw\nyMZIMwux8gsgawVtU1u6MmIB9px4JncFepsg3FdSEbqdYZL3MeExDT7PPh2GQcbS\nfcl1gYTrCgJFUZUr2JBOSVIoIvGATH7VIMYBWantbAiQgGqkJstXb8UngEM4hrsX\nuQIDAQAB\n-----END PUBLIC KEY-----";
let result;
try {
result = originalPublicDecrypt.call(this, ...args);
let data = JSON.parse(result.toString());
result = Buffer.from(JSON.stringify(data));
crypto.log("Decrypt by activator success")
} catch (e) {
crypto.log("Decrypt by activator failed, fallback to officail decrypt")
result = null;
let ori = originalPublicDecryptEx(args[1]);
crypto.log(
"Decrypt Error, ",
args[1].toString("base64"),
"\nOfficial decrypt result: ",
ori,
"\nDetail:\n",
e
);
result = ori;
}
return result;
};
},
});
Object.defineProperty(crypto, "log", {
get() {
return function log(...args) {
console.log(...args);
};
},
});
const electron = require("electron");
const originalNet = electron.net;
const originalRequest = originalNet.request;
Object.defineProperty(originalNet, "request", {
get() {
return function (options, callback) {
let url = options["url"];
if (url.indexOf("/_api/share/vana_map/?lang=zh") > -1) {
} else url = url.replace("https://www.xmind.cn", `http://${hostname}:${server_port}`);
options["url"] = url;
const req = originalRequest(options, callback);
// response event listener
req.on("response", (response) => {
let data = "";
response.on(
"data",
function (chunk) {
data += chunk;
chunk = "FUCKING data";
this.emit("continue", chunk);
}.bind(response)
);
response.on(
"end",
function () {
// persist data to cache
// cache[options.url] = data;
console.log(
"===== Intercepting net.request with options:",
options,
"Response ----- ",
data
);
this.emit("continue");
}.bind(response)
);
});
return req;
};
},
});
破解脚本代码
const asar = require('asar');
const fs = require('fs');
const { exit } = require('process');
const path = `${process.env.HOME||process.env.USERPROFILE}/AppData/Local/Programs/Xmind/resources`
const extratFolder = 'dist'
function extractCode() {
if (!fs.existsSync(path)) {
console.error('[!] Error: Could not found XMind Installation.')
exit(-1)
}
console.log('[*] Info : Start extracting...')
asar.extractAll(`${path}/app.asar`, `${path}/${extratFolder}/`)
if (!(fs.existsSync(`${path}/${extratFolder}/main`) && fs.existsSync(`${path}/${extratFolder}/renderer`) && fs.existsSync(`${path}/${extratFolder}/static`) && fs.existsSync(`${path}/${extratFolder}/package.json`))){
console.error('[!] Error: Extract error')
exit(-1)
}
console.log('[*] Info : Extract finish')
}
function replaceCertificatePublicKey() {
const searchString = Buffer.from('String.fromCharCode(45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,71,102,77,65,48,71,67,83,113,71,83,73,98,51,68,81,69,66,65,81,85,65,65,52,71,78,65,68,67,66,105,81,75,66,103,81,67,68,89,72,51,49,108,48,108,108,105,99,66,97,118,98,85,90,82,103,48,121,49,76,110,73,10,50,74,74,117,80,90,97,107,48,52,57,56,119,71,109,75,48,78,43,107,115,113,67,122,65,48,88,85,102,67,103,81,53,69,57,105,116,89,121,80,117,84,43,122,54,80,122,47,43,48,113,54,78,101,65,112,107,87,99,110,67,47,84,104,10,87,81,89,54,90,108,69,79,77,111,110,114,104,80,117,98,56,122,115,87,89,79,90,122,99,107,81,117,116,120,51,106,110,54,107,43,54,90,88,120,55,121,85,98,98,107,120,73,107,43,119,113,87,103,110,108,81,120,110,120,54,84,77,100,10,83,51,114,103,111,51,114,52,98,108,70,84,87,105,54,69,69,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)')
const replaceString = Buffer.from('String.fromCharCode(45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,113,73,71,102,103,104,120,115,47,115,99,104,106,105,100,43,109,72,108,75,10,65,81,104,87,72,109,49,122,49,117,80,88,47,67,87,114,50,84,66,72,80,99,103,51,80,68,109,70,56,118,86,105,121,104,117,120,112,107,86,101,52,47,88,52,84,122,57,104,78,57,66,71,65,43,104,55,116,111,72,85,119,54,114,75,10,122,50,77,107,53,77,53,112,101,71,53,73,100,52,68,86,76,65,68,117,86,100,112,99,98,106,111,48,89,112,99,48,109,79,100,68,68,84,74,116,108,99,50,84,56,113,49,48,114,100,71,89,68,48,69,114,112,101,82,57,83,117,57,105,10,97,74,120,68,87,77,79,76,108,78,122,112,109,87,88,112,103,75,81,87,106,82,117,122,111,73,114,79,105,105,72,118,71,122,65,105,83,114,67,77,75,116,54,109,43,47,109,43,83,118,114,53,67,81,72,119,43,47,74,120,49,105,65,119,10,121,77,90,73,77,119,117,120,56,103,115,103,97,119,86,116,85,49,117,54,77,109,73,66,57,112,120,52,74,110,99,70,101,112,115,103,51,70,100,83,69,98,113,100,89,90,76,51,77,101,69,120,68,84,55,80,80,104,50,71,81,99,98,83,10,102,99,108,49,103,89,84,114,67,103,74,70,85,90,85,114,50,74,66,79,83,86,73,111,73,118,71,65,84,72,55,86,73,77,89,66,87,97,110,116,98,65,105,81,103,71,113,107,74,115,116,88,98,56,85,110,103,69,77,52,104,114,115,88,10,117,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)')
function walkDir(dir) {
let list = []
fs.readdirSync(dir).forEach((item) => {
list.push(item)
})
return list
}
const baseDir = `${path}/${extratFolder}/renderer`
let jsFile = walkDir(baseDir).filter((name) => name.endsWith('.js'))
console.log('[*] Info : Replace software authentic certificate...')
let isHited = false
for (let index = 0; index < jsFile.length; index++) {
let content = fs.readFileSync(`${baseDir}/${jsFile[index]}`)
let foundIndex = content.indexOf(searchString)
if (foundIndex != -1) {
console.log(`[*] Info : Target hit in file ${jsFile[index]}`)
const finalContent = content.subarray(0, foundIndex) + replaceString + content.subarray(foundIndex + searchString.length)
fs.writeFileSync(`${baseDir}/${jsFile[index]}`, finalContent)
console.log('[*] Info : Replace certificate finish')
isHited = true
break
}
}
if (!isHited) {
console.log('[!] Error: Target not found, exited.')
exit(-1)
}
}
function writeActivator() {
console.log('[*] Info : Applying Activator...')
if (fs.existsSync(`${__dirname}/activator.js`)) {
} else {
console.log('[!] Error: Activator lost')
exit(-1)
}
fs.copyFileSync(`${__dirname}/activator.js`, `${path}/${extratFolder}/main/activator.js`)
console.log('[*] Info : Apply activator finish')
console.log('[*] Info : Register activator...')
fs.writeFileSync(`${path}/${extratFolder}/main/main.js` ,Buffer.from(`require('./activator')\n`) + fs.readFileSync(`${path}/${extratFolder}/main/main.js`))
console.log('[*] Info : Register finish')
}
function cleanup() {
console.log('[*] Info : Deleting code dir...')
fs.rmSync(`${path}/${extratFolder}`, {force: true, recursive: true})
if (fs.existsSync(`${path}/${extratFolder}`)) {
console.log(`[^] Warn : Cleanup failed, some file still in "${path}/${extratFolder}", delete it manually`)
} else {
console.log('[*] Info : Cleanup finish')
}
}
function repack() {
console.log('[*] Info : Back up official software')
fs.renameSync(`${path}/app.asar`, `${path}/app.asar.bak`)
console.log(`[*] Info : Your software backup is "${path}/app.asar.bak", if something wrong, rename it to app.asar`)
console.log('[*] Info : Repacking software...')
asar.createPackage(`${path}/${extratFolder}/`, `${path}/app.asar`).finally(() => {
// cleanup
cleanup()
if (fs.existsSync(`${path}/app.asar`)) {
console.log('[*] Info : Repack finish, now you could open XMind and input any account with any password to upgrade it with pro edition')
} else {
console.log('[!] Error: Repack failed, rollback...')
fs.renameSync(`${path}/app.asar.bak`, `${path}/app.asar`)
console.log('[*] Info : Rollback finish. Activation progress has failed, try it again.')
}
})
}
// extract code first
extractCode()
// replace certificate
replaceCertificatePublicKey()
// write activator
writeActivator()
// repack
repack()
使用方法
- 将注册器与破解脚本都写入到同一目录下
- 安装
asar
,命令为:npm install asar
- 运行破解脚本,命令为:
node ./crack.js