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

查看 233|回复 9
作者:QiuChenly   
Xmind macOS & Windows (23.05|2005) 通杀方案


16843184094761.jpg (438.39 KB, 下载次数: 0)
下载附件
2023-5-17 21:53 上传

阅前须知
[ol]
  • 不接受Dinner。
  • 本帖与相关代码仅用于个人研究学习逆向技术使用 禁止二次打包出售/传播破解成品。
  • 账号密码都是 123,登录成功即可破解。有账号的退出重登,没账号的记得一定要登录,否则不会激活。
  • 我是神里绫华小姐的狗。
  • 上次聊的那个小护士好像对我不怎么来电,楼主很愤怒,索性就破解一个 XMind 助助兴吧。
  • asar需要自己安装 nodejs最新版本即可,然后执行 npm i -g asar 就安装好了。可以在 cmd/bash 直接调用 asar 解包。
    [/ol]
    前言 陨落的天才
    "破之力,-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 实现了全平台。


    16843240651983.jpg (343.38 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传

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


    16843241781884.jpg (236.13 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传

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


    16843242235498.jpg (923.01 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传



    16843242813701.jpg (879.25 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传

    经过进一步的搜索,发现 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)
    代码来自这里,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 来自于抓包。


    16843254634029.jpg (122.48 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传

    {"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。


    16843267162708.jpg (169.51 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传

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


    16843267612014.jpg (680.13 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传

    我们已经成功注入进去代码,实现了无侵入式修改。
    下一步,我们伪造 Http 返回值:


    16843270887720.jpg (627.05 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传

    利用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,我们运行看看:


    16843274838200.jpg (1.02 MB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传



    16843275045674.jpg (532.81 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传

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


    16843276919847.jpg (59.36 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传

    对于这种情况我们分析代码可知:
    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")"即可。


    16843293355286.jpg (370.58 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传

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


    16843293910524.jpg (556.36 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:54 上传

    成功拿下。
    压下内心的激动,少年用力的喝了口自来水。想起父母临别前的叮嘱:“三年内千万不要去报仇!时机尚未成熟,尔等还需隐忍三年!”
    少年闭上了双眼,三年.....
    0x02 打包&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文件完整路径


    LB87(`X4N{72SP{0XQNMF2R.png (146.23 KB, 下载次数: 0)
    下载附件
    2023-5-17 23:52 上传

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


    T}04~OXQXJ14)~CT$N2{S5G.png (171.25 KB, 下载次数: 0)
    下载附件
    2023-5-17 23:52 上传

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


    G3]VV~M]CEEJ(_$(TKP}W~4.png (609.58 KB, 下载次数: 0)
    下载附件
    2023-5-17 23:52 上传

    完成后如图所示。
    打开main.js 在头部加入一行"require("./hook");",记住千万要顶部加入一行,并且结尾要有";"号,防止编译出错。至于为什么不加分号编译会出错,懂得都懂。
    接下来Sublime Text 搜索替换所有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)
    替换示例:


    2TDLE3K3W1ZVOF9TV3V8TNF.png (708.02 KB, 下载次数: 0)
    下载附件
    2023-5-17 23:59 上传

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


    0ae2d8fd708126a156abb158648e3ba9.png (819.13 KB, 下载次数: 0)
    下载附件
    2023-5-17 21:53 上传

    "这不可能!"Xmind丶顶针珍珠惊呼道,惊骇溢于言表!台下更是爆发出阵阵尖叫!
    “回来了,一切都回来了!”
    少年站在聚光灯下,享受着这awesome的moment,哈哈狂笑道:“你....输了!”
    Credit&Other
    学习 Hook 文件:

    Hook.zip
    (3.52 KB, 下载次数: 34, 售价: 1 CB吾爱币)
    2023-5-17 21:52 上传
    点击文件名下载附件
    售价: 1 CB吾爱币         [记录]
    [购买]
    下载积分: 吾爱币 -1 CB

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


    68f9f5d17652a95844fbce5d579d718f_0.jpg (247.98 KB, 下载次数: 0)
    下载附件
    2023-5-17 22:05 上传

    下载次数, 少年

  • tianwenmingce   

    。。。还能说啥呢,二次元无敌(破音ing)~
    mootutu   


    tianwenmingce 发表于 2023-5-17 22:04
    。。。还能说啥呢,二次元无敌(破音ing)~

    拼拼拼拼拼拼拼拼
    best_919   

    技术大牛!!! 膜拜
    忆年   

    虽然我看不懂但我大受震撼
    deadlybugs   

    太牛了,绝对精华帖
    lsyh1688   

    不疯不成魔?!
    imosx   

    秋神牛逼
    waxxy   

    二次元楼主太强大了!感谢分享
    夏橙M兮   

    看不懂,大为震撼,这个windows版本的到底怎么用呢?
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部