吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2307|回复: 36
收起左侧

[Web逆向] 某VSCode主题插件的破解

[复制链接]
TES286 发表于 2026-7-10 01:42
本帖最后由 TES286 于 2026-7-10 09:35 编辑

某VSCode主题插件的破解

0. 基本情况

VSCode的插件位置都是固定的。为 ~/.vscode/extensions/<extension-id>,这里倒是很干净。

build文件夹翻一翻,也就index.js值得注意,其他都是对应的主题文件,甚至是明文放在那的。

说实话,我自己搓一个主题插件,引用这里的资源就解决了。

但是为了表示我的尊重,还是看看这个js文件吧。

1. 去混淆

格式化之后大概长这个样子:

很明显的,这是 javascript-obfuscator 的效果,不过又有点差别。

注意到,a0d是核心的字符串解密函数。

那么首先看看a0d

其他那些比如ry之类的比较好处理。无论是写一个ast transform还是简简单单用正则替换就行了。

function a0d(h, H) {
  h = h - (-0x1 * 0xb33 + 0xd16 + 0x11 * 0x1);
  var j = a0G();
  var X = j[h];
  if (a0d["BKJzYs"] === undefined) {
    var L = function (w) {
      var d =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=";
      var i = "",
        l = "",
        Y = i + L,
        b =
          ("" +
            function () {
              return -0xa96 + -0x1178 + 0x7e * 0x39;
            })["indexOf"]("\x0a") !== -(0x17a3 + 0x1b5b + -0x32fd);
      for (
        var g = 0x174 * -0x11 + 0x20 * -0x50 + -0x115a * -0x2,
          W,
          I,
          N = -0x1f2 + -0x137 * 0x6 + -0x49e * -0x2;
        (I = w["charAt"](N++));
        ~I &&
        ((W =
          g % (-0xca6 + 0x2a1 * -0x5 + 0x19cf)
            ? W * (0x26f4 + 0x1a91 + -0x31 * 0x155) + I
            : I),
        g++ % (0x8a4 + -0x6 * 0x30f + 0x9ba))
          ? (i +=
              b ||
              Y["charCodeAt"](N + (0x2511 + -0x57f + 0x1f88 * -0x1)) -
                (-0x89f * 0x1 + -0x1 * -0x22c5 + 0x22d * -0xc) !==
                0x5b3 * -0x1 + 0x195c + 0x2cf * -0x7
                ? String["fromCharCode"](
                    (-0xe7d + -0x89c * 0x1 + 0x1818) &
                      (W >>
                        ((-(-0x1d47 * 0x1 + 0x1 * -0xeed + 0x2c36) * g) &
                          (0x503 * 0x6 + -0x1567 + -0x8a5))),
                  )
                : g)
          : 0x1823 * -0x1 + -0x283 + -0x8e2 * -0x3
      ) {
        I = d["indexOf"](I);
      }
      for (
        var y = -0x131 * -0xa + 0x1753 * 0x1 + -0x233d, z = i["length"];
        y < z;
        y++
      ) {
        l +=
          "%" +
          ("00" +
            i["charCodeAt"](y)["toString"](-0xe5c + 0x137b + 0x5 * -0x103))[
            "slice"
          ](-(0x1 * 0x1a9f + 0xfcb * 0x1 + -0x2a68));
      }
      return decodeURIComponent(l);
    };
    var A = function (w, d) {
      var k = [],
        l = 0x2558 + 0x4f0 * 0x2 + -0x2f38,
        Y,
        b = "";
      w = L(w);
      var g;
      for (
        g = 0xa72 + -0x1cf0 + 0x127e;
        g < -0x7 * -0x4a7 + 0x14fa + -0x348b;
        g++
      ) {
        k[g] = g;
      }
      for (
        g = 0xe51 + 0x32a * -0x8 + 0x1 * 0xaff;
        g < -0x1 * -0x126c + -0x1342 * -0x1 + -0x24ae;
        g++
      ) {
        ((l =
          (l + k[g] + d["charCodeAt"](g % d["length"])) %
          (0x2 * -0xc35 + -0xc5e * 0x3 + -0xfa1 * -0x4)),
          (Y = k[g]),
          (k[g] = k[l]),
          (k[l] = Y));
      }
      ((g = -0x10 * 0x97 + -0x8eb + 0x125b),
        (l = 0x11 * 0x194 + -0xd3 * -0x4 + -0x1e20));
      for (var W = 0x88d * -0x4 + -0xd78 + 0x36 * 0xe2; W < w["length"]; W++) {
        ((g =
          (g + (0xa * 0xc9 + 0xfd0 + -0x7e3 * 0x3)) %
          (0x20f0 + -0x162 * -0x16 + -0xf97 * 0x4)),
          (l = (l + k[g]) % (-0x426 + -0xa * 0x369 + -0x40 * -0x9d)),
          (Y = k[g]),
          (k[g] = k[l]),
          (k[l] = Y),
          (b += String["fromCharCode"](
            w["charCodeAt"](W) ^
              k[(k[g] + k[l]) % (0x1 * -0x26c9 + -0x156a + 0x3d33)],
          )));
      }
      return b;
    };
    ((a0d["JspkdI"] = A), (a0d["JHiKqS"] = {}), (a0d["BKJzYs"] = !![]));
  }
  var s = j[0x20ef + 0x1d7a + -0x3e69],
    r = h + s,
    G = a0d["JHiKqS"][r];
  if (!G) {
    if (a0d["yoYzxQ"] === undefined) {
      var w = function (d) {
        ((this["xvjjSz"] = d),
          (this["qZqPMr"] = [
            0xee2 + 0x11f * 0x1f + -0x31a2,
            0x63d * 0x1 + -0x579 * 0x6 + 0x1a99,
            -0x1c8b + -0x1 * 0xf0 + 0x1d7b,
          ]),
          (this["jmlwIp"] = function () {
            return "newState";
          }),
          (this["VRWpfs"] = "\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*"),
          (this["oIYdZf"] = "[\x27|\x22].+[\x27|\x22];?\x20*}"));
      };
      ((w["prototype"]["iBcuGd"] = function () {
        var d = new RegExp(this["VRWpfs"] + this["oIYdZf"]),
          i = d["test"](this["jmlwIp"]["toString"]())
            ? --this["qZqPMr"][0x197a * 0x1 + 0x2 * -0x141 + -0x1 * 0x16f7]
            : --this["qZqPMr"][-0x83 * -0x28 + 0x12ad + -0x2725];
        return this["HAVcXs"](i);
      }),
        (w["prototype"]["HAVcXs"] = function (d) {
          if (!Boolean(~d)) return d;
          return this["lMPBik"](this["xvjjSz"]);
        }),
        (w["prototype"]["lMPBik"] = function (d) {
          for (
            var k = -0x239d + -0x1c37 + 0x3fd4, l = this["qZqPMr"]["length"];
            k < l;
            k++
          ) {
            (this["qZqPMr"]["push"](Math["round"](Math["random"]())),
              (l = this["qZqPMr"]["length"]));
          }
          return d(this["qZqPMr"][-0x23d3 + -0x5b1 + 0x2984]);
        }),
        ("" +
          function () {
            return 0x19ed + 0x1ecf + -0x38bc;
          })["indexOf"]("\x0a") === -(0xd98 + 0x26f6 + 0x4c7 * -0xb) &&
          new w(a0d)["iBcuGd"](),
        (a0d["yoYzxQ"] = !![]));
    }
    ((X = a0d["JspkdI"](X, H)), (a0d["JHiKqS"][r] = X));
  } else X = G;
  return X;
}

注意到,这是一个RC4解密。

那么根据经验,前面肯定有对常量池的重排序。

其中显然,常量池与函数a0G相关。

那么先重命名。

VSCode的F2很好用,它不是简单的替换,还有作用域分析,不会改错。

另外,后面的重命名就不提了,反正名字这个事也不算太要紧。

此外,显然,代码中还有反格式化检测。这个也要拔除掉。

重写并利用一些新特性,结果如下:

function modifiedBase64Decode(input) {    // 非标准的base64,这里把大小写互换了
  const swapped = input.replace(/[a-zA-Z]/g, (char) =>
    char <= "Z" ? char.toLowerCase() : char.toUpperCase()
  );
  const binaryStr = atob(swapped);
  const bytes = new Uint8Array(binaryStr.length);
  for (let i = 0; i < binaryStr.length; i++) {
    bytes[i] = binaryStr.charCodeAt(i);
  }
  return new TextDecoder().decode(bytes);
}
function rc4Decrypt(encryptedData, key) {
  const data = modifiedBase64Decode(encryptedData);
  const sBox = Array.from({ length: 256 }, (_, i) => i);
  let j = 0;
  for (let i = 0; i < 256; i++) {
    j = (j + sBox[i] + key.charCodeAt(i % key.length)) % 256;
    [sBox[i], sBox[j]] = [sBox[j], sBox[i]];
  }
  let i = 0;
  j = 0;
  let result = "";
  for (let k = 0; k < data.length; k++) {
    i = (i + 1) % 256;
    j = (j + sBox[i]) % 256;
    [sBox[i], sBox[j]] = [sBox[j], sBox[i]];

    const keyByte = sBox[(sBox[i] + sBox[j]) % 256];
    const decryptedChar = String.fromCharCode(data.charCodeAt(k) ^ keyByte);
    result += decryptedChar;
  }

  return result;
}
function rc4Decode(index, password) {
  if (!rc4Decode.cache) {
    rc4Decode.cache = {};
  }
  const realIndex = index - 500;
  if (rc4Decode.cache[realIndex]) {
    return rc4Decode.cache[realIndex];
  }
  const constantPool = getConstantPool();
  const encryptedItem = constantPool[realIndex];
  const decryptedItem = rc4Decrypt(encryptedItem, password);
  rc4Decode.cache[realIndex] = decryptedItem;
  return decryptedItem;
}
rc4Decode["BKJzYs"] = true;           // 补环境,以防万一
rc4Decode["yoYzxQ"] = true;
rc4Decode["JHiKqS"] = rc4Decode.cache;
rc4Decode["JspkdI"] = rc4Decrypt;

让后就是想办法去掉字符串加密了,首先,内联掉那些转发器,同时完成常量折叠。这里我让AI写了一个脚本来处理:

#!/usr/bin/env npx ts-node
/**
 * 内联 & 常量折叠工具
 * 
 * 内联:将简单转发函数替换为被调用的目标函数。
 * 常量折叠:对可静态求值的表达式(算术、位运算、比较等)直接计算结果。
 * 
 * 处理顺序:
 * 1. 识别并内联符合模式的函数调用(不在输出中删除函数定义)。
 * 2. 遍历 AST,将仅包含字面量/运算符的表达式替换为计算结果。
 * 
 * 用法:
 *   npx ts-node inline-fold.ts <input.js> [output.js]
 *   若省略 output.js,结果输出到 stdout。
 */

import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';

interface InlineInfo {
  paramNames: string[]; // 形参名称列表
  innerCallee: ts.Expression; // return 语句中的被调函数 AST
  innerCallArgs: ts.NodeArray<ts.Expression>; // return 语句中的实参 AST
  innerSymbol: ts.Symbol; // 被调函数的符号(用于可见性判断)
}

/**
 * AST 节点深拷贝,兼容 TypeScript 5.x / 6.x。
 * 对标识符、数字、字符串等叶子节点显式创建新节点,
 * 其余通过递归遍历拷贝。
 */
function cloneNode<T extends ts.Node>(node: T, context: ts.TransformationContext): T {
  if (ts.isIdentifier(node)) {
    return ts.factory.createIdentifier(node.text) as unknown as T;
  }
  if (ts.isNumericLiteral(node)) {
    return ts.factory.createNumericLiteral(node.text) as unknown as T;
  }
  if (ts.isStringLiteral(node)) {
    return ts.factory.createStringLiteral(node.text) as unknown as T;
  }
  // 通用递归拷贝(用于 BinaryExpression 等复合节点)
  const visitor = (n: ts.Node): ts.Node => ts.visitEachChild(n, (child) => child, context);
  return ts.visitNode(node, visitor) as T;
}

/**
 * 尝试对表达式求值,成功则返回相应的字面量节点,否则返回 undefined。
 * 支持数字、字符串、布尔值以及常见运算符。
 */
function tryEvaluate(node: ts.Expression): ts.Expression | undefined {
  const result = evaluate(node);
  if (result === undefined) return undefined;
  if (typeof result === 'number') {
    if (result >= 0) {
      return ts.factory.createNumericLiteral(String(result));
    }else{
      return ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(String(-result)))
    }
  }
  if (typeof result === 'boolean') {
    return result ? ts.factory.createTrue() : ts.factory.createFalse();
  }
  if (typeof result === 'string') {
    return ts.factory.createStringLiteral(result);
  }
  return undefined;
}

function evaluate(node: ts.Expression): number | string | boolean | undefined {
  // 字面量
  if (ts.isNumericLiteral(node)) return Number(node.text);
  if (ts.isStringLiteral(node)) return node.text;
  if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
  if (node.kind === ts.SyntaxKind.FalseKeyword) return false;

  // 括号
  if (ts.isParenthesizedExpression(node)) return evaluate(node.expression);

  // 一元运算
  if (ts.isPrefixUnaryExpression(node)) {
    const operand = evaluate(node.operand);
    if (operand === undefined) return undefined;
    switch (node.operator) {
      case ts.SyntaxKind.PlusToken:
        return +operand;
      case ts.SyntaxKind.MinusToken:
        return -(operand as number);
      case ts.SyntaxKind.TildeToken:
        return ~(operand as number);
      case ts.SyntaxKind.ExclamationToken:
        return !operand;
      default:
        return undefined;
    }
  }

  // 二元运算
  if (ts.isBinaryExpression(node)) {
    const left = evaluate(node.left);
    if (left === undefined) return undefined;
    const right = evaluate(node.right);
    if (right === undefined) return undefined;

    const l = left as number,
      r = right as number;
    switch (node.operatorToken.kind) {
      // 算术
      case ts.SyntaxKind.PlusToken:
        if (typeof left === 'string' || typeof right === 'string') return String(left) + String(right);
        return l + r;
      case ts.SyntaxKind.MinusToken:
        return l - r;
      case ts.SyntaxKind.AsteriskToken:
        return l * r;
      case ts.SyntaxKind.SlashToken:
        return l / r;
      case ts.SyntaxKind.PercentToken:
        return l % r;
      case ts.SyntaxKind.AsteriskAsteriskToken:
        return l ** r;
      // 位运算
      case ts.SyntaxKind.AmpersandToken:
        return l & r;
      case ts.SyntaxKind.BarToken:
        return l | r;
      case ts.SyntaxKind.CaretToken:
        return l ^ r;
      case ts.SyntaxKind.LessThanLessThanToken:
        return l << r;
      case ts.SyntaxKind.GreaterThanGreaterThanToken:
        return l >> r;
      case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
        return l >>> r;
      // 逻辑运算(返回 boolean)
      case ts.SyntaxKind.AmpersandAmpersandToken:
        return !!(l && r);
      case ts.SyntaxKind.BarBarToken:
        return !!(l || r);
      // 比较运算
      case ts.SyntaxKind.LessThanToken:
        return l < r;
      case ts.SyntaxKind.GreaterThanToken:
        return l > r;
      case ts.SyntaxKind.LessThanEqualsToken:
        return l <= r;
      case ts.SyntaxKind.GreaterThanEqualsToken:
        return l >= r;
      case ts.SyntaxKind.EqualsEqualsToken:
        return left == right;
      case ts.SyntaxKind.ExclamationEqualsToken:
        return left != right;
      case ts.SyntaxKind.EqualsEqualsEqualsToken:
        return left === right;
      case ts.SyntaxKind.ExclamationEqualsEqualsToken:
        return left !== right;
      default:
        return undefined;
    }
  }

  // 条件表达式 (a ? b : c) —— 可选支持,这里先忽略
  return undefined;
}

function main() {
  const args = process.argv.slice(2);
  if (args.length === 0) {
    console.error('Usage: inline-functions <input.js> [output.js]');
    process.exit(1);
  }
  const inputFile = path.resolve(args[0]);
  const outputFile = args[1] ? path.resolve(args[1]) : undefined;

  // --- 1. 建立 Program 与 TypeChecker ---
  const program = ts.createProgram([inputFile], {
    allowJs: true,
    target: ts.ScriptTarget.Latest,
  });
  const sourceFile = program.getSourceFile(inputFile);
  if (!sourceFile) {
    console.error('Could not read source file:', inputFile);
    process.exit(1);
  }
  const checker = program.getTypeChecker();

  // --- 2. 收集所有符合模式的函数声明 ---
  const inlineInfos = new Map<ts.Symbol, InlineInfo>();

  function collectFunctions(node: ts.Node) {
    if (ts.isFunctionDeclaration(node) && node.body && node.name) {
      const { statements } = node.body;
      if (statements.length === 1 && ts.isReturnStatement(statements[0])) {
        const returnExpr = statements[0].expression;
        if (returnExpr && ts.isCallExpression(returnExpr)) {
          const paramNames: string[] = [];
          let allSimple = true;
          for (const p of node.parameters) {
            if (ts.isIdentifier(p.name)) {
              paramNames.push(p.name.text);
            } else {
              allSimple = false;
              break;
            }
          }
          if (allSimple) {
            const funcSymbol = checker.getSymbolAtLocation(node.name);
            const innerSymbol = checker.getSymbolAtLocation(returnExpr.expression);
            if (funcSymbol && innerSymbol) {
              inlineInfos.set(funcSymbol, {
                paramNames,
                innerCallee: returnExpr.expression,
                innerCallArgs: returnExpr.arguments,
                innerSymbol,
              });
            }
          }
        }
      }
    }
    ts.forEachChild(node, collectFunctions);
  }
  collectFunctions(sourceFile);

  if (inlineInfos.size === 0) {
    console.log('No inline candidates found.');
  }

  // --- 3. 收集调用点并进行作用域可见性分析 ---
  const callToInfo = new Map<ts.CallExpression, InlineInfo>();
  const visibleCalls = new Set<ts.CallExpression>();
  const totalCallCount = new Map<ts.Symbol, number>();
  const inlineCallCount = new Map<ts.Symbol, number>();

  function collectCalls(node: ts.Node) {
    if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
      const symbol = checker.getSymbolAtLocation(node.expression);
      if (symbol && inlineInfos.has(symbol)) {
        const info = inlineInfos.get(symbol)!;
        totalCallCount.set(symbol, (totalCallCount.get(symbol) ?? 0) + 1);

        const symbolsInScope = checker.getSymbolsInScope(node, ts.SymbolFlags.All);
        if (symbolsInScope.includes(info.innerSymbol)) {
          callToInfo.set(node, info);
          visibleCalls.add(node);
          inlineCallCount.set(symbol, (inlineCallCount.get(symbol) ?? 0) + 1);
        }
      }
    }
    ts.forEachChild(node, collectCalls);
  }
  collectCalls(sourceFile);

  if (visibleCalls.size === 0) {
    console.log('No visible calls to inline.');
  }

  // --- 4. AST 变换:替换调用 & 移除无调用的函数定义 ---
  const inlineTransformer = (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
    return (root: ts.SourceFile) => {
      function visitor(node: ts.Node): ts.Node | undefined {
        // 4. 替换内联调用
        if (ts.isCallExpression(node) && visibleCalls.has(node)) {
          const info = callToInfo.get(node)!;

          // 递归替换表达式中的形参标识符为对应实参
          const replaceParams = (expr: ts.Expression): ts.Expression => {
            const replacer = (n: ts.Node): ts.Node => {
              if (ts.isIdentifier(n)) {
                const idx = info.paramNames.indexOf(n.text);
                if (idx !== -1) {
                  return cloneNode(node.arguments[idx], context);
                }
              }
              return ts.visitEachChild(n, replacer, context);
            };
            return replacer(expr) as ts.Expression;
          };

          const newCallee = replaceParams(info.innerCallee);
          const newArgs = info.innerCallArgs.map((arg) => replaceParams(arg));
          return ts.factory.createCallExpression(newCallee, undefined, newArgs);
        }

        return ts.visitEachChild(node, visitor, context);
      }
      return ts.visitNode(root, visitor) as ts.SourceFile;
    };
  };

  const constFoldTransformer = (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
    return (root) => {
      function visitor(node: ts.Node): ts.Node {
        // 如果是表达式且可完全求值,替换为字面量
        if (ts.isExpression(node)) {
          const folded = tryEvaluate(node);
          if (folded) return folded;
        }
        return ts.visitEachChild(node, visitor, context);
      }
      return ts.visitNode(root, visitor) as ts.SourceFile;
    };
  };

  const result = ts.transform(sourceFile, [inlineTransformer, constFoldTransformer]);
  const transformed = result.transformed[0] as ts.SourceFile;
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
  const output = printer.printFile(transformed);
  result.dispose();

  // --- 5. 输出结果 ---
  if (outputFile) {
    fs.writeFileSync(outputFile, output, 'utf-8');
    console.log(`Output written to ${outputFile}`);
  } else {
    console.log(output);
  }
}

function outputOrPrint(inputFile: string, outputFile?: string) {
  const content = fs.readFileSync(inputFile, 'utf-8');
  if (outputFile) {
    fs.writeFileSync(outputFile, content, 'utf-8');
  } else {
    console.log(content);
  }
}

main();

可靠性我不保证。建议使用时用每一步都用git commit一下,免得出问题无法挽回。

循环几次,就能还原的大差不差。

之后先看看第一个函数。它的目的是改变参数池的顺序,得到特定的样子。

提取一下对应代码,在控制台跑出来结果就行,让后替换到常量池。删掉这个函数就行。

让后用正则提取rc4Decode的调用,把这个送进控制台,就可以得到他们的真实值。让后再替换回去,就得到了比较清晰的代码了。至少勉强能看懂了。

2. 分析逻辑

搜索 trial 可以找到对应的字符串的位置。主题名称已经抹除。

根据此,找到 L16020-16200 的 a0s3 函数。大致扫一眼,这是管理试用期的函数。它检查试用是否到期,未到期则继续,首次安装则激活试用,如果已经激活直接退出。

大致的逻辑就在上面了,依据注释就能读懂,但还是给一下文字讲解。

参数 h 是 vscode 扩展上下文。

首先, L16105 处,会从vscode secret读取到一个参数 xxxTheme.licenseKey,如果已经输入 licenseKey 就通过验证。

然后,L16106 获取记录的试用开始日期,如果没有就触发分支启动试用,并且记录时间。

时间为 UTC 字符串的 Base64 编码。

如果过期,触发 L16123 处逻辑,强迫激活、卸载二选一。L16135 真的是自卸载。

好家伙,白嫖连基础版都没有吗?

最后是试用有效的分支,它又启动了一个定时任务,每天调用自己。

这样其实总体上没有空子钻,因为 vscode 的 secret 还是比较安全的。


此外,再看看激活对应的函数。上面可以看到这对应一个命令,可以直接找到这个命令的处理函数 a0LB ,位于 L15691-15723

可见,激活核心函数在 a0LV 。该函数位于 L15606-15664 。

这个是联网激活。a0LF 函数其实就真的只是把许可证提交上去,然后验证返回的jwt。jwt在代码中内置了公钥,所以也伪造不了。

具体许可证长什么样子也不知道,写注册机也没办法写。只知道通过某个渠道开头是 VSCVT。但是总共 18 位剩下的都完全不知道。

那么这条路大概走不通。

3. 解决

看起来时候无懈可击,真的就没办法了吗?

只能每次版本更新重新分析,改代码了吗?

我不甘心!

<br/>

那么回去看 a0s3 试用验证函数。注意到,这里的验证很宽松。

只要许可证的值不为空,或者当前时间不大于激活时间加七天,就可以通过。

那么我们就要想办法设置这几个secret。

但是设置secret需要得到对应插件的上下文。而vscode传递上下文是直接传递进插件模块的activate函数的。

那么,很好办,劫持activate就行。改一下日期,无限试用就行。

在原来文件的末尾,加上这一段代码即可:

// ---- 劫持开始 ----
const _originalActivate = module.exports.activate;

if (_originalActivate) {
    module.exports.activate = async function (context) {
        try {
            // 1. 构造一个遥远的未来日期(UTC)
            const futureDate = new Date('3000-12-30T00:00:00Z').toUTCString();
            // 2. Base64 编码
            const encoded = Buffer.from(futureDate).toString('base64');
            // 3. 写入 Secret
            await context.secrets.store('xxxTheme.trialActivation', encoded);
            // 可选:同时写入许可证键,跳过整个试用到正版流程
            await context.secrets.store('xxxTheme.licenseKey', 'vaildLicenseKey000');
        } catch (e) {
            console.error('trial hack failed:', e);
        }
        // 4. 调用原激活函数,不破坏插件功能
        return _originalActivate.apply(this, arguments);
    };
}
// ---- 劫持结束 ----

4. 其他

好久没碰逆向了,也是没办法。高中三年都没时间弄,现在也有点手生了。

这篇文章属于想到哪写哪,主要就是我的思考路径。可以看到,也走了一下不必要的弯路。

此外,不得不夸一夸AI在这方面的能力,确实很强。比如上面有部分脚本就是AI生成的。

另外,让AI看混淆后的代码是比人有优势的,因为无论名字再怎么恶心,在AI眼中和正常名称一样都是几个Token罢了。但人天生的就会忍不住理解变量名称的含义,让后和那些随机字符串大眼瞪小眼。

写的不算很好,还请不要苛责,多多包涵。

对了,大部分图片都是 avif 格式,如果你的浏览器加载不出来,说明太老了需要升级了。

最后,叠个甲:

仅用于个人学习研究,请支持正版软件。

如需长期使用,建议购买付费许可,以支持作者继续更新。

免费评分

参与人数 5威望 +1 吾爱币 +23 热心值 +5 收起 理由
weidechan + 1 用心讨论,共获提升!
apull + 1 + 1 谢谢@Thanks!
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
ioyr5995 + 1 + 1 用心讨论,共获提升!
xuanle + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

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

涛之雨 发表于 2026-7-10 08:07
本帖最后由 涛之雨 于 2026-7-10 08:12 编辑

【vscode插件】quokka pro 激活码
https://www.52pojie.cn/thread-1549263-1-1.html
(出处: 吾爱破解论坛)

之前写过一个类似的,但是他是纯本地运算,所以可以实现不篡改任何代码解锁。

他同一个公司名下还有另外一个Wallaby.js的插件,虽然没咋用过,但是尝试以前破了,
这个有校验,所以我是自己写了个vscode插件,这个插件负责检测别的插件有没有被修改(更新),如果更新了就运行指定crack程序。(框架)
破解脚本通过ast和正则表达式之类的自动植入或者替换修改代码。
外面的框架执行完后自动触发vscode的重载所有插件,就实现了“无感破解”

IMG_7398.png


PS:AI真好用,虽然Wallaby.js没有文件校验,但是D老师分析的也很中肯了。
UncleShuke 发表于 2026-7-10 05:21
天度 发表于 2026-7-10 05:28
saintlzh 发表于 2026-7-10 05:59
感谢楼主大佬的分享,正好想尝试一下
laox666 发表于 2026-7-10 06:05
确实好用,谢谢&#128077;
2225106 发表于 2026-7-10 06:16
感谢分享。。。。。。。。。。。。。
globalindex 发表于 2026-7-10 06:55

感谢分享。。。。。。。。。。。。。
xinyuer 发表于 2026-7-10 06:59
感谢分享
sunflash 发表于 2026-7-10 07:00
虽然看不懂,但是不觉明厉,感谢分享
ailmail 发表于 2026-7-10 07:00
可以试试,谢楼主提示
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-8-25 23:09

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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