吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 16062|回复: 359
上一主题 下一主题
收起左侧

[MacOS逆向] Xmind macOS & Windows (23.05|2005) 通杀方案

    [复制链接]
跳转到指定楼层
楼主
QiuChenly 发表于 2023-5-17 21:57 回帖奖励
本帖最后由 QiuChenly 于 2023-5-23 03:13 编辑

Xmind macOS & Windows (23.05|2005) 通杀方案

阅前须知

  1. 不接受Dinner。
  2. 本帖与相关代码仅用于个人研究学习逆向技术使用 禁止二次打包出售/传播破解成品。
  3. 账号密码都是 123,登录成功即可破解。有账号的退出重登,没账号的记得一定要登录,否则不会激活。
  4. 我是神里绫华小姐的狗。
  5. 上次聊的那个小护士好像对我不怎么来电,楼主很愤怒,索性就破解一个 XMind 助助兴吧。
  6. asar需要自己安装 nodejs最新版本即可,然后执行 npm i -g @electron/asar 就安装好了。可以在 cmd/bash 直接调用 asar 解包。
  7. Windows版本添加hook文件并替换公钥后如果出现弹窗激活/卡死/白屏是你的问题。我专门重启到Windows下测试过自己测试怎么打开都没问题,我认为是你操作有问题。
    他的卡死原因只有一个,那就是替换完公钥保存时vscode自动格式化了js代码,自动格式化可能会破坏js文件的代码执行流程,强调过了不要用vscode自动格式化!!!
    就用Sublime Text之类的文本编辑器暴力替换就可以了!不要动任何js文件!
    特别支持:现已支持 XMind 的在线图库更新。

23.05.2661  Win/Mac版本通杀

因为上一版本 RSA 公钥我更改了 所以这次使用新的 RSA 公钥串激活

  1. 替换文件的更新:
    2661 版本 Hook 参考文件:
    23.05.2661 Hook.zip (7.92 KB, 下载次数: 297, 售价: 1 CB吾爱币)

  2. 新的公钥替换:
    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)

    替换为新的公钥:
    注意 新版本只替换 common.js 里面的一处即可,因为他新版本重构之后屎山代码终于没了,只需要替换 common.js  一个文件就够了
    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)

其他的操作与下面一模一样,只需要注意公钥是新的以及使用新的 hook 文件即可。

前言 陨落的天才

"破之力,-2147483647段!"
冷眼看着CTF全球破解Rank天梯排位上的最终定段排位,那意味着羞辱的白色文字白的刺眼,少年面无表情,只有紧握着的双手青筋绽出能表达出少年内心的痛苦,甚至由于用力过大,指甲刺进了手心也恍然未觉.

"秋城落叶,破之力,-2147483647段,级别: 超高校级史前巨低!" Rank 天梯排位主持人语气淡漠的念出了少年的最终排位,冰冷的话语代表着的事实再次让少年本就破碎淋漓的心微微抽痛!

主持人刚脱口而出这个消息,便在Rank排位赛上的选手中就引起了一阵骚动。

“-2147483647?嘿嘿,这b成绩寄存器都给他干爆了吧?”
“哎,这废物真是把我们二次元的脸丢光了。”
“要不是三年前这“天才”干碎过Adobe全家桶和几个大型商业软件,这种fw连进CTF的资格都没有,哪还有机会上来丢我们CTF全球大赛的脸?纯属拉低我们大赛的含金量!”
“哎,昔日谈笑间令反汇编代码灰飞烟灭 闻名二次元世界的天才💻程序员少年怎么落魄到这种程度?”
“谁知道呢?或许是天天上p站小蓝鸟🐦‍⬛,又没有女朋友👧,现在只知道打胶🧠脑子打坏了吧...大家要记得戒色😍啊...”
“看他那个衰样,肥头大耳油腻的头发,纯死肥宅一个,光看外表就真下头...”

周围传来的😕不屑和🤭嘲笑和幸灾乐祸,全都落在少年那敏锐的顺风耳中,犹如一柄利剑,狠狠的刺穿心脏❤️,令少年双目👀不禁微微赤红。

少年👦缓缓抬起头来,露出一张肥头大耳油腻🧍‍♂️脸庞来,浑浊发黄的双眸狠狠的扫过这些嘲笑他的臭鱼🐟烂虾🦐们,少年嘴角的自嘲,更加苦涩。

“集爸们谁懂啊?今天Rank排位定级赛,碰到一堆下头男,对我冷嘲热讽...🆘9命啊集霸🚪!”

苦涩一笑,落寞的转身走进臭鱼烂虾人群中,心中却下定决心要干出一番大事,狠狠的打脸今日羞辱他的众人!

“顶针珍珠,破之力,2147483647段!义演顶针,鉴定为玩原神玩的!峡谷之巅最强王者!”主持人一改禁欲播报风,双眼火热的注视着这位新晋Top1!

“不愧是Xmind集团扶持的种子选手,一手bytecode技术让人无从下手,真了不起!”
“不愧是年轻一代顶级强者啊...”

强忍着悲痛的少年落寞的背影留在了Xmind丶顶针珍珠的眼中,不屑之色更重,心中冷笑道:普段は綺麗な顔立ちなのにフェラ顔は超下品!証拠を残さないためにフェラごっくんはもちろん、中出しで体内に**隠しまで!【究極のこっそり】堪能してみませんか?

三年前那意气风发的少年,一岁扣字,三岁征战孙吧,五岁楼中楼和网友对喷5000层,年仅十岁就破解了众多商业软件,凭借手中一台800块钱的i386电脑成功登顶CTF亚洲赛区最年轻的最强王者!

然而天才的道路充满曲折,三年前声望达到巅峰的天才少年,突兀的接触到了房赌毒,收到了有生以来最残酷的打击,京海市市中心房价年年增高,买了Xmind集团房烂尾上诉无果后又无家可归,只能天天刷推,还每天p站固定冲浪2小时,一夜之间梦想和大好前途化为乌有,而破之力技术也诡异的随着时间流逝越变越少!

破之力消失的直接结果,便是其实力不断倒退,去年还是-65534,直到今日竟然逼近了 int 数据类型最小值!

从天才的神坛,一夜跌落到了连普通人都不如的地步,这种打击让少年失魂落魄,天才の名,也逐渐被😕不屑和👎嘲讽取代。

站的越高摔的越狠,这次的跌落,或许再也没有爬起来的机会!

0x01 复仇计划

寂静的夜晚月光和一个寂寞的灵魂。
山崖之巅,秋城落叶躺在草地上,👄+🥬最终叼着一根青草,任由那微微的酸涩在口中爆开。

“那Xmind财团不仅夺走了我的一切,还扶持了一个同样的天才少年,真是可恶!怎么才能...”少年恶狠狠的想着,看着模糊的夜色,突然眼中一亮,一个恶毒的计划在心中开始酝酿...

却说那 Xmind乃是思维导图行业小有盛名的公司,而旗下的 Xmind 产品更是使用了 Node 字节码技术保护了主程序,让无数天才少年铩羽而归,一举奠定了反破解的巅峰!

而此刻昏黄的灯光下,少年面前的MacBook Pro 中运行的正是XMind!
少年此时正在操作 asar 进行解包,那 Xmind 却是依赖了 Electron 技术,本质上还是 Vue3 + Pinia 实现了全平台。

随后少年用 Visual Studio Code 打开 app 文件夹,开始查阅反编译的代码.

经过少年的观察,这里的升级至Pro很有重大嫌疑。

经过进一步的搜索,发现 Xmind 4412 行代码中认为this.activationStatus === u.ACTIVATION_STATUS.VALID 表示激活,否则显示激活按钮。

而this.activationStatu来自 e.status ,搜索一番后发现:

              E = (0, i.computed)(() => {
                const e = (0, s.useAccountStore)();
                if (!e.rawSubscriptionData) return null;
                try {
                  return N(e.rawSubscriptionData);
                } catch (e) {
                  return null;
                }
              });
              S = (0, i.computed)(() => {
                if (!E.value) return c.ACTIVATION_STATUS.TRIAL;
                {
                  const { status: e, expireTime: t } = E.value && E.value;
                  if (e && e === c.SUBSCRIPTION_SERVER_STATUS.EXPIRED)
                    return c.ACTIVATION_STATUS.EXPIRED;
                  if (e && e === c.SUBSCRIPTION_SERVER_STATUS.VALID)
                    return t && p.value && new Date(t) < new Date(p.value)
                      ? c.ACTIVATION_STATUS.EXPIRED
                      : c.ACTIVATION_STATUS.VALID;
                }
              }),

代码来自这里,E.value 如果为 NULL,则返回试用ACTIVATION_STATUS.TRIAL,所以我们需要关注N(e.rawSubscriptionData)这个数据。

函数 N 如下:

d=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);

              N = (e) => {
                const t = Buffer.from(e, "base64"),
                  n = a.default.publicDecrypt(
                    { key: d, padding: a.default.constants.RSA_PKCS1_PADDING },
                    t
                  );
                return JSON.parse(n.toString());
              };

而 d 实际上值为
'-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDYH31l0llicBavbUZRg0y1LnI\n2JJuPZak0498wGmK0N+ksqCzA0XUfCgQ5E9itYyPuT+z6Pz/+0q6NeApkWcnC/Th\nWQY6ZlEOMonrhPub8zsWYOZzckQutx3jn6k+6ZXx7yUbbkxIk+wqWgnlQxnx6TMd\nS3rgo3r4blFTWi6EEQIDAQAB\n-----END PUBLIC KEY-----'

由此可见,这是一个 RSA 公钥,然后我们用他解密我们的e.rawSubscriptionData 试试。
e.rawSubscriptionData 来自于抓包。

{"status": "Trial", "expireTime": 0, "ss": "", "deviceId": ""},status 如果为“sub”表示订阅有效,expireTime 表示到期时间,其他值不管。

所以我们只要伪造返回数据即可,自己生成一对密钥,然后伪造加密数据:
我的密钥对
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALuQXELwuGkDD+IYTrrSNgztWK6pdbM3bBDWVtQeP6oJWbY10PCP24Wy
kxo/6nn0xRDFM4Y+QKMrztEb64JvpKxUI4QFnn67PDJtW3QnvvQNJKzO+xWFDNKZ
wB5kPZ0WGROBgAWjp2/v6hUN/1+JWoIwDilpa0LwHZ8OMvcyu/6lAgMBAAE=
-----END RSA PUBLIC KEY-----

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC7kFxC8LhpAw/iGE660jYM7ViuqXWzN2wQ1lbUHj+qCVm2NdDw
j9uFspMaP+p59MUQxTOGPkCjK87RG+uCb6SsVCOEBZ5+uzwybVt0J770DSSszvsV
hQzSmcAeZD2dFhkTgYAFo6dv7+oVDf9fiVqCMA4paWtC8B2fDjL3Mrv+pQIDAQAB
AoGAVJClyFiYDGChDKNA++JDFFj+nuEwe/kE9CJvS3vH4HYOyKRC6/MwWntE75TZ
ttqw7vq6XFA8/FSIDqez6z9C0tlo1Gj1qIVFSmqeDaq1DoECFtkAIfSKmMbea8nL
AshUlPiKZ7msDq38+GQmVIHvfOrN8iiyC3Jr39Z2szEN8BECQQDt8m8evi1PFoNg
TgO4a+szLHGt85ztHDOgm3OfftqSC1TL9hpAgRyIrjCukfIYNGQhyAm6RfhmE3Fu
06xFkRhbAkEAyctaIMSC9FPY/CL1MYKSRvS7ZZYoHh8DZF/NCnt9EmyEM3KPM/xJ
IKTO6UxKiqfGAtAUMLiBoyu9Y0rU5Fr0/wJANMTdC85VMgLmI8dpX87fHDwxAcjS
9mqYsHeJDsgNJPJKXek4LTH06ALpXO2U6PVFd5BrR9oYmlqZf2CGBe+FnQJBAJCc
0IwnCAn8hMW8b6b5gcaj4CAfCcT8SLwIA7L9aFZpuhv8fy+sHuPr9/QtHkZbkYW2
hKGduBmtYN3lZMf5fxUCQHRhDYJe4nVVw7spQRf5zwni4xUuTFicDMaiMLedTLBF
I7a+DNlOoXgdhlO4uivv4IPcWaRCe3/HdzJobZ8FmxQ=
-----END RSA PRIVATE KEY-----

我伪造的信息
{"status": "sub", "expireTime": 4093057076000, "ss": "", "deviceId": ""}

加密后信息
eeZRXhL4ZY6ftIFDi1JU9XA1mqJaUuiJFgmZySEz50u/HW31e4Tucf4jkCXPRJO3fsLcUYXgK9fjY4H6FnUK4Wh5xBxAdUx+3p986xXZg85fEKtyxyZmuCAff8MNvOBsOLxmJkN2i4+iyuDGQkmhhFx3k60RkeczyV80BM9lbWI=

下面就是考虑怎么替换这个返回值。

少年微微皱眉,分析到现在,却没有任何实质性进展,不禁有些急躁。

0x02 利用NodeJS模块缓存进行Hook

如本文标题所见,本文主要是通杀 Windows 版本,而 Windows 版本有 bytecode字节码加密,所以不能像 macOS 上修改 js 一样轻松。

const crypto = require("crypto");

// 保存原始的 publicDecrypt 函数
const originalPublicDecrypt = crypto.publicDecrypt;

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;
};

// 将 publicDecrypt 函数定义为 getter 方法,返回新的实现
Object.defineProperty(crypto, "publicDecrypt", {
  get() {
    return function myPublicDecrypt(...args) {
      console.trace("myPublicDecrypt 调用栈");
      console.log("秋城落叶Hook Xmind开始");

      args[0]["key"] =
        "-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBALuQXELwuGkDD+IYTrrSNgztWK6pdbM3bBDWVtQeP6oJWbY10PCP24Wy\nkxo/6nn0xRDFM4Y+QKMrztEb64JvpKxUI4QFnn67PDJtW3QnvvQNJKzO+xWFDNKZ\nwB5kPZ0WGROBgAWjp2/v6hUN/1+JWoIwDilpa0LwHZ8OMvcyu/6lAgMBAAE=\n-----END RSA PUBLIC KEY-----";
      let result;
      try {
        result = originalPublicDecrypt.call(this, ...args);
        let data = JSON.parse(result.toString());
        data.status = "sub";
        data.expireTime = 4093057076000;
        result = Buffer.from(JSON.stringify(data));
        crypto.log("用自己的密钥解密成功,开始走我的密钥解密流程。", data);
      } catch (e) {
        crypto.log("解密出错,开始走官方密钥解密流程。");
        result = null;
        let ori = originalPublicDecryptEx(args[1]);
        crypto.log(
          "解密出错",
          args[1].toString("base64"),
          "\n官方密钥解密结果",
          ori,
          "\n错误细节\n",
          e
        );
        result = ori;
      }

      // 调用原始的 publicDecrypt 函数
      return result;
    };
  },
});

Object.defineProperty(crypto, "log", {
  get() {
    return function log(...args) {
      console.log(...args);
    };
  },
});

module.exports = crypto;

我们知道 nodejs 中有一个概念叫模块缓存,这是为了优化性能而设计的。
当我们下一次 require 某个模块的时候,会从缓存里去读取模块缓存代码,而这正好为我们的攻击提供了便利。
让我们来看上方一段代码,利用模块重导出技术我们成功 Hook 了 Main.js 并修改了加密函数的 key 为我们自己的 key。


我们只需要在 main.js 文件头部加上一行引入即可。

运行试试:

我们已经成功注入进去代码,实现了无侵入式修改。

下一步,我们伪造 Http 返回值:

利用NodeJS的内部模块,我们直接监听了本地一个 socket 端口,实现了一个简易服务器,地址为: 127.0.0.1:3000.

然后分别判断 req 来源的 path 判断请求的 Http 地址是什么,并返回伪造好的数据。

const http = require("http");
const url = require("url");

const hostname = "127.0.0.1";
const port = 3000;

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: "QiuChenly@52pojie",
        fullname: "QiuChenly@52pojie",
        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: "QiuChenly@52pojie",
        fullname: "QiuChenly@52pojie",
        type: null,
      })
    );
  } else if (path === "/_res/devices" && method === "POST") {
    res.statusCode = 200;
    res.end(
      JSON.stringify({
        raw_data:
          "eeZRXhL4ZY6ftIFDi1JU9XA1mqJaUuiJFgmZySEz50u/HW31e4Tucf4jkCXPRJO3fsLcUYXgK9fjY4H6FnUK4Wh5xBxAdUx+3p986xXZg85fEKtyxyZmuCAff8MNvOBsOLxmJkN2i4+iyuDGQkmhhFx3k60RkeczyV80BM9lbWI=",
        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}/`);
});

require("./hook/crypto");
require("./hook/electron");

然后利用上面说到的代码注入技术 hook掉 electron.net.request 包的请求,拦截网络请求并修改域名:

const electron = require("electron");

// 获取原始的 net 模块
const originalNet = electron.net;

// 保存原始的 request 函数
const originalRequest = originalNet.request;

// 修改 request 函数
Object.defineProperty(originalNet, "request", {
  get() {
    return function (options, callback) {
      options["url"] = options["url"].replace(
        "https://www.xmind.cn",
        "http://127.0.0.1:3000"
      );
      console.error(
        "===== Intercepting net.request with options:",
        options,
        callback
      );
      const req = originalRequest(options, callback);

      // 注册 response 事件监听器
      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 () {
            // 将数据添加到缓存
            // cache[options.url] = data;
            // console.log("Response ----- ", data);
            this.emit("continue");
          }.bind(response)
        );
      });
      return req;
    };
    return function (options, ...args) {
      // 对 options 进行修改或者添加自己的逻辑
      console.error("===== Intercepting net.request with options:", options);

      // { url: 'https://www.xmind.cn/_res/user_sub_status', method: 'GET' }
      // { url: 'https://www.xmind.cn/_res/devices', method: 'POST' }

      // 调用原始的 request 函数
      return originalRequest.call(this, options, ...args);
    };
  },
});

module.exports = electron;

同样 require,我们运行看看:



可以看出菜单已经没有 VIP 提示了,但是主界面还有升级到 Pro 的按钮。

对于这种情况我们分析代码可知:

E = (0, i.computed)(() => {
                const e = (0, s.useAccountStore)();
                if (!e.rawSubscriptionData) return null;
                try {
                  return N(e.rawSubscriptionData);
                } catch (e) {
                  return null;
                }
              }),

s.useAccountStore这里就是前端的 localStorage 存储,所以肯定是读取的我们伪造的信息毋庸置疑,但是为什么还提示升级?其实是我们的公钥Hook没有覆盖到 renderer 层js代码,所以我们手动替换所有的公钥:

=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,82,83,65,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,71,74,65,111,71,66,65,76,117,81,88,69,76,119,117,71,107,68,68,43,73,89,84,114,114,83,78,103,122,116,87,75,54,112,100,98,77,51,98,66,68,87,86,116,81,101,80,54,111,74,87,98,89,49,48,80,67,80,50,52,87,121,10,107,120,111,47,54,110,110,48,120,82,68,70,77,52,89,43,81,75,77,114,122,116,69,98,54,52,74,118,112,75,120,85,73,52,81,70,110,110,54,55,80,68,74,116,87,51,81,110,118,118,81,78,74,75,122,79,43,120,87,70,68,78,75,90,10,119,66,53,107,80,90,48,87,71,82,79,66,103,65,87,106,112,50,47,118,54,104,85,78,47,49,43,74,87,111,73,119,68,105,108,112,97,48,76,119,72,90,56,79,77,118,99,121,117,47,54,108,65,103,77,66,65,65,69,61,10,45,45,45,45,45,69,78,68,32,82,83,65,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)

下载我写好的 js 文件,解压到 main文件夹内,然后在 main.js 文件头部增加一行"require("./hook")"即可。


这里不用 vscode 替换,因为 vscode换完会自动格式化代码,导致代码出现异常,所以用文本编辑器暴力替换。

替换完打包重新运行看看:

成功拿下。

压下内心的激动,少年用力的喝了口自来水。想起父母临别前的叮嘱:“三年内千万不要去报仇!时机尚未成熟,尔等还需隐忍三年!”

少年闭上了双眼,三年.....

0x03 打包&Windows Hook

这三年少年或游山玩水,或进入红尘,不仅没有浇灭内心复仇的火焰,反而想要在下一届CTF破之力天梯排位上手刃仇人的心越发坚定!

三年之期已到,龙王出世!

2023届全球CTF大会依然是热闹非凡,有众多顶级大手子群英荟聚。而聚光灯下,少年静静坐在台上,冷冷的看着 XMind丶顶针珍珠。

“这场 Rank 由废柴之称的“秋城落叶”和Xmind丶顶针珍珠,最强者和最弱者的对决究竟是一方完全惨败还是另一方一鸣惊人?让我们拭目以待!”主持人声嘶力竭的吼道,话音刚落台下便爆发出了尖叫声,显然顶针珍珠的粉丝在为他加油呐喊!

“这1!次,我要狠狠的测测你的瑞克5!”Xmind丶顶针珍珠不屑的说。

这一次,我要拿回属于我的荣耀!
少年眼神火热的看着 CTF 大会最高荣誉奖杯,冷笑道:“当年你欺我年少无知,今日我便双手奉还!”

少年冷笑一声,便排出四文大钱,高声叫到:“你可知道Nodejs的js有几种Hook劫持方法?”
话音刚落,便展示出 macOS 上的完整 XMind 破解版,让台下数万观众一起尖叫起来!

“是他!他回来了!那个男人回来了!”
“没错!这个自信睥睨的气息!是他!就是他!”
“没想到三年后竟然回到巅峰!此子实力竟然恐怖如斯!”
”本以为又是一场毫无悬念的Rank,没想到曾经陨落的天才居然绝地反击!实力更胜从前!“
”阿弥陀佛,这就是戒色😍的功劳,大家记得要戒色😍!“

台下观众的骚动没有影响到少年分毫,而此时顶针珍珠却是满头大汗!没想到这小子竟然偷偷恢复了实力!当下却也强撑道:”这macOS版本不过是没有加密罢了,且看你如何破我的Windows版本bytecode!“

原来这顶针珍珠自信无人能修改 bytecode,当下放下了心,却看到少年那脸上的冷笑,心中咯噔一跳,难道....

"我早知道尔等要垂死挣扎,看招!" 少年冷笑着打开 Parallels Desktop 18.1.1,这也是少年当年巅峰时期的得意之作

没想到Windows 下 Hook 破解的操作竟和 macOS 下一模一样!真的做到的通杀!

临时文件夹的路径=随便找个目录 比如D:/code即可
app.asar的路径=Xmind 安装目录中的 resources 文件夹中的app.asar文件完整路径


只见少年熟练的在 Windows 下安装 nodejs最新版本,cmd执行 npm i -g @electron/asar 安装 asar 工具包,随后打开 cmd 执行 "asar extract app.asar的路径 临时文件夹的路径"解包 asar 文件为源代码。

extract表示解包
pack表示打包
得到了 asar 解包后的文件,照旧将附件解压出来的hook.js文件和hook文件夹复制到asar文件解包出来的 main文件夹中


完成后如图所示。
打开main.js 在头部加入一行"require("./hook");",记住千万要顶部加入一行,并且结尾要有";"号,防止编译出错。至于为什么不加分号编译会出错,懂得都懂。

复制文件只是第一步注入,第二步是替换所有 js 文件里面的公钥为我自己的公钥:

接下来Sublime Text/VSCode 搜索替换所有js里面的的公钥为我的 RSA 公钥,具体操作和替换的代码在上面有,仔细查看。全部替换并保存所有文件后并打包回 app.asar就完成了!

解包出来所有的js文件批量搜索替换即可,不用一个个去替换。


=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,82,83,65,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,71,74,65,111,71,66,65,76,117,81,88,69,76,119,117,71,107,68,68,43,73,89,84,114,114,83,78,103,122,116,87,75,54,112,100,98,77,51,98,66,68,87,86,116,81,101,80,54,111,74,87,98,89,49,48,80,67,80,50,52,87,121,10,107,120,111,47,54,110,110,48,120,82,68,70,77,52,89,43,81,75,77,114,122,116,69,98,54,52,74,118,112,75,120,85,73,52,81,70,110,110,54,55,80,68,74,116,87,51,81,110,118,118,81,78,74,75,122,79,43,120,87,70,68,78,75,90,10,119,66,53,107,80,90,48,87,71,82,79,66,103,65,87,106,112,50,47,118,54,104,85,78,47,49,43,74,87,111,73,119,68,105,108,112,97,48,76,119,72,90,56,79,77,118,99,121,117,47,54,108,65,103,77,66,65,65,69,61,10,45,45,45,45,45,69,78,68,32,82,83,65,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45)

说白了就是搜索替换文本,把上面的fromCharCode替换成下面的fromCharCode。

还有,如果搜索结果搜索不到这串公钥说明你搜索范围有问题,我要你搜索的是 main文件夹同级的renderer文件夹内的所有 js 文件,而且不要用 vscode 格式化 js 文件!这些 js 文件应该是压缩好的,格式化js文件后会搜索不到!!


替换示例:

打包: cmd执行 "asar pack 临时文件夹的路径 app.asar的路径"
打开 Xmind 后登录账号 123 密码 123

随后便用力打开xmind:

"这不可能!"Xmind丶顶针珍珠惊呼道,惊骇溢于言表!台下更是爆发出阵阵尖叫!


“回来了,一切都回来了!”
少年站在聚光灯下,享受着这awesome的moment,哈哈狂笑道:“你....输了!”

0x04 Credit&Other

学习 Hook 文件:

Hook.zip (3.52 KB, 下载次数: 531, 售价: 1 CB吾爱币)

今天被包工头👷‍♀️骂了,说我水泥拌的太稀了。
包工头把我的铁锹锤烂了,问我水是不是不要钱💰。
我不敢反驳,他👷‍♀️不知道我只是拌水泥的时候很想你👧,眼泪😭掉进了水泥里。

DFF580830B99AD16942588178D4BD301.jpg (518.29 KB, 下载次数: 5)

DFF580830B99AD16942588178D4BD301.jpg

8268A55372DC5F5226CA4171D4460897.jpg (56.82 KB, 下载次数: 0)

8268A55372DC5F5226CA4171D4460897.jpg

B6F950163E1DC6C5C3183699CB461833.jpg (73.35 KB, 下载次数: 6)

B6F950163E1DC6C5C3183699CB461833.jpg

免费评分

参与人数 145吾爱币 +140 热心值 +129 收起 理由
ixcw52 + 1 + 1 所有娱乐网站都删了,以后就指着楼主的更新丰富精神生活了。
Marken888 + 1 + 1 我很赞同!
justvanity + 1 + 1 大佬 请收下我的膝盖
cxg863 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
小Jamie + 1 + 1 谢谢@Thanks!
WYW + 1 + 1 谢谢@Thanks!
sanstuby + 1 谢谢@Thanks!
b12312312 + 1 + 1 热心回复!
zzhaoxue + 1 + 1 热心回复!
wainia333 + 1 + 1 热心回复!
zyl162180 + 1 + 1 我很赞同!
bj201809 + 1 + 1 谢谢@Thanks!
朝闻道夕死可矣 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
comeondbb + 1 + 1 哥她配不上你!
x175141 + 1 + 1 我很赞同!
ljq18888 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
hackcat + 1 + 1 大佬牛逼,好文章值得收藏
zeroy0011 + 1 用心讨论,共获提升!
bulaqitegu + 1 + 1 热心回复!
蒙蒙plus + 2 + 1 谢谢@Thanks!
KevinChiang + 1 谢谢@Thanks!
gdqb521 + 1 用心讨论,共获提升!
睡个安稳觉 + 1 + 1 我很赞同!
笙若 + 1 + 1 谢谢@Thanks!
yxw1112 + 1 + 1 赞同
lazygoat644 + 1 + 1 谢谢@Thanks!
jackies + 1 + 1 谢谢@Thanks!
ddklky + 1 + 1 这不是天才是什么,这小子这文本这才华,这不直接起飞啊
QAQ~QL + 1 + 1 我很赞同!
¥Crack¥ + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
agonyperkey + 1 我很赞同!
liaaaa2 + 1 + 1 谢谢@Thanks!
mofox + 1 + 1 谢谢@Thanks!
隔壁村的小逗比 + 1 + 1 谢谢@Thanks!
BenJM + 1 + 1 我很赞同!
Cool_风 + 1 + 1 我很赞同!
Levince + 1 + 1 我很赞同!
heiyomao + 1 + 1 谢谢@Thanks!
hwj逆光飞翔 + 1 + 1 谢谢@Thanks!
monkey9981 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
xychromosome + 1 + 1 我很赞同!
chinanober + 1 用心讨论,共获提升!
四夕人云 + 1 + 1 我很赞同!
黄教主 + 1 我很赞同!
wy741344140 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
wangyongdesign + 1 + 1 谢谢@Thanks!
搞点什么 + 1 + 1 牛!!!
YinYaokun + 1 + 1 我很赞同!
Allendale + 1 太牛了
xiaojue染柒 + 1 用心讨论,共获提升!
sadanlei + 1 + 1 谢谢@Thanks!
wenshao1344 + 1 + 1 用心讨论,共获提升!
Sryu + 1 + 1 热心回复!
Janling + 1 + 1 用心讨论,共获提升!
denglintao + 2 + 1 我很赞同!
Surprise. + 1 + 1 我很赞同!
a954210336 + 1 + 1 &amp;lt;font style=&amp;quot;vertical-align: inherit;&amp;quot;&amp;gt;&amp;lt;font style=
Bluesky10 + 1 + 1 热心回复!
魔道书生 + 2 + 1 人才
O大表哥O + 1 好活!当赏!
duokebei + 1 谢谢@Thanks!
znyue + 1 + 1 卧槽 我一个小白都成功了,大神
浪漫前奏 + 1 + 1 用心讨论,共获提升!
xiangyujayfun + 1 + 1 我很赞同!
break995 + 1 + 1 用心讨论,共获提升!
轩可雅 + 1 谢谢@Thanks!
asdly1992 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Akisa + 1 + 1 谢谢@Thanks!
gun008 + 1 人才
观弈山人 + 1 我很赞同!
fdy8421 + 1 + 1 热心回复!
一指流年 + 1 + 1 谢谢@Thanks!
常常喜乐 + 1 + 1 流僻!
sludger + 1 + 1 用心讨论,共获提升!
abz + 1 + 1 学习学习
xyt0142 + 1 + 1 我很赞同!
慢慢走,欣赏啊 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
MichaelWin + 1 谢谢@Thanks!
liker5092 + 1 + 1 我很赞同!
三滑稽甲苯 + 2 + 1 用心讨论,共获提升!
xljh888 + 1 + 1 谢谢@Thanks!
Cleverwwh + 1 + 1 我很赞同!
Ls雷 + 1 + 1 好活!当赏!
wxywrxjj + 1 + 1 好活!当赏!
bitter841129 + 1 + 1 所有娱乐网站都删了,以后就指着楼主的更新丰富精神生活了。
gamezx + 1 少年你真NB
云天灬 + 1 + 1 不明觉厉!
tunis + 1 + 1 我很赞同!
laomasanli + 1 + 1 神,大神
凡凡之呗 + 2 + 1 我很赞同!
小色虫 + 1 大佬 来个PD18
qimomo99 + 1 我很赞同!
smile1110 + 3 + 1 谢谢@Thanks!
hihand2000 + 1 + 1 我很赞同!
cfxxh300 + 1 逆向一小时,排版两小时,水贴半天
one88736 + 1 我很赞同!
cwtbr + 1 + 1 谢谢@Thanks!
aa20221101 + 1 + 1 热心回复!
zhuyu005 + 1 + 1 看不懂就对了
番茄煎蛋 + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

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

来自 #
 楼主| QiuChenly 发表于 2023-5-30 11:17 |楼主
本帖最后由 QiuChenly 于 2023-5-30 11:28 编辑
darkreg 发表于 2023-5-30 00:35
文件版本为23.5.2.2660.35.189
产品版本为:23.5.26660.202305210741



你是真的牛逼。
帖子里让你用 nodejs 里面的 asar工具包解包,谁教你的用 7z?
我还真以为xmind更新了直接连js 引擎的 loader 都不用写直接运行字节码了。
谁教你用 7z 解压的??
谁教你用 7z 解压的??
谁教你用 7z 解压的??
谁教你用 7z 解压的??
谁教你用 7z 解压的??
这是压缩包吗?这是压缩包吗就解压??
不跟着帖子来瞎操作!

免费评分

参与人数 1热心值 +1 收起 理由
darkreg + 1 我很赞同!

查看全部评分

推荐
MYPcodcsja 发表于 2023-5-18 01:09
QiuChenly 发表于 2023-5-18 01:06
1.搜索范围应该在 renderer 文件夹内
2.不要 vscode 直接打开 main  文件夹,你这样搜得到才有鬼了。公 ...

我没找到所谓的官方公钥,在将hook文件放入main文件夹中并且将「require("./hook");」写入到头部文件中后,直接打包临时文件夹中的文件为新的 app.asar 文件,并替换官方原文件,然后打开xmind并且登录后,会提示已经升级为Xmind Pro,虽然主界面还是有升为Xmind PRO的标志,但是实际上已经可以使用Pro功能了。








123.png (329.97 KB, 下载次数: 0)

123.png

免费评分

参与人数 3吾爱币 +3 热心值 +1 收起 理由
QiuChenly + 1 + 1 帖子里写了搜的是 renderer 文件夹 仔细看贴行不
darkreg + 1 最新下载mind的app.asar解压出来没有main文件夹
9A91 + 1 用心讨论,共获提升!

查看全部评分

推荐
夏橙M兮 发表于 2023-5-17 23:06
看不懂,大为震撼,这个windows版本的到底怎么用呢?
推荐
fjq0215 发表于 2023-5-18 01:37
我去,愣是没看懂是怎么破解
推荐
m1y3ll0w 发表于 2023-5-31 21:23
Fankilair 发表于 2023-5-31 14:02
2660版的安装包能分享一下吗

官方直链:
https://dl2.xmind.cn/Xmind-for-Windows-x64bit-23.05.2660-202305210741.exe

修改后面的版本号即可(前提是得在网上搜到版本号)

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Fankilair + 1 + 1 谢谢@Thanks!

查看全部评分

推荐
忆年 发表于 2023-5-17 22:37
虽然我看不懂但我大受震撼
推荐
tianwenmingce 发表于 2023-5-17 22:04
。。。还能说啥呢,二次元无敌(破音ing)~
推荐
 楼主| QiuChenly 发表于 2023-5-18 01:06 |楼主
MYPcodcsja 发表于 2023-5-18 00:54
提示未找到结果,在源文件里面找也没找到官方公钥,是怎么回事呢?QAQ

1.搜索范围应该在 renderer 文件夹内
2.不要 vscode 直接打开 main  文件夹,你这样搜得到才有鬼了。公钥在 main 文件夹同级的 renderer 文件夹内,应该用 vscode 打开 extract 出来的文件夹而不是 asar 解包出来的文件夹中的main 文件夹!
推荐
johna 发表于 2023-5-17 23:20
确实牛!学习了,谢谢分享!
5#
mootutu 发表于 2023-5-17 22:18
tianwenmingce 发表于 2023-5-17 22:04
。。。还能说啥呢,二次元无敌(破音ing)~

拼拼拼拼拼拼拼拼
6#
best_919 发表于 2023-5-17 22:36
技术大牛!!! 膜拜
7#
deadlybugs 发表于 2023-5-17 22:40
太牛了,绝对精华帖
8#
lsyh1688 发表于 2023-5-17 22:45
不疯不成魔?!
9#
imosx 发表于 2023-5-17 22:48
秋神牛逼
10#
waxxy 发表于 2023-5-17 22:48
二次元楼主太强大了!感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2023-6-2 05:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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