标签 JavaScript混淆 下的文章

众所周知,Anyrouter 用了 acw_sc__v2 动态 Cookie 做反爬,导致查询余额脚本、签到脚本都需要这个动态的 Cookie,这里记录一下怎么绕过。

虽然站内已经有现成的方案,但是这位佬的无法支持 Cloudflare Worker 部署,着实有些不方便

混淆代码分析

当 Cookie 中不存在 acw_sc__v2 时,返回的 HTML 大致结构如下:

<html> <script>var arg1 = ;
(function(a, c) {
// 大量混淆代码...
}(a0i, ), !(function() {
// 核心逻辑在这里
}()));
function a0i() { /* 字符串数组 */ }
function a0j(a, b) { /* 解码函数 */ }
</script> </html>

代码使用了常见的混淆技术:

  • 字符串数组 + 解码函数
  • 变量名替换为十六进制
  • 控制流平坦化

核心算法提取

去除混淆后,核心逻辑如下:

// 输入:arg1 是一个 40 位的十六进制字符串 var arg1 = ;

// 置换表(固定) var m = [0xf, , , , , , , ,
         0xa, , , , , , , ,
         , 0xd, , 0xb, , , , ,
         0xe, , , , , , , ,
         , , , , , , 0xc, ];

// XOR 密钥(固定) var p = ;

// Step 1: unsbox - 字符重排 var q = [];
for (var x = 0; x < arg1.length; x++) {
    for (var z = 0; z < m.length; z++) {
        if (m[z] == x + 1) {
            q[z] = arg1[x];
        }
    }
}
var u = q.join('');

// Step 2: hexXor - 十六进制异或 var v = '';
for (var i = 0; i < u.length; i += 2) {
    var a = parseInt(u.slice(i, i + 2), 16);
    var b = parseInt(p.slice(i, i + 2), 16);
    var xored = (a ^ b).toString(16);
    if (xored.length == 1) xored = '0' + xored;
    v += xored;
}

// 设置 Cookie document.cookie = 'acw_sc__v2=' + v + '; expires=...';
document.location.reload();

算法详解

Step 1: unsbox(字符重排)

置换表 m 定义了一个映射关系:m[z] = x + 1 表示输出的第 z 位来自输入的第 x 位。

换句话说,m 数组的值表示 "从输入的哪个位置取字符":

  • m[0] = 0xf = 15,输出第 0 位 = 输入第 15 位
  • ,输出第 1 位 = 输入第 35 位

简化实现:

const unsboxed = m.map(i => arg1[i - 1]).join('');

Step 2: hexXor(十六进制异或)

将重排后的字符串与固定密钥逐字节异或:

unsboxed:  XOR key:  result:  

异或运算每次处理 2 个十六进制字符(1 字节):

完整实现(TypeScript)

const XOR_KEY = ;
const UNSBOX_TABLE = [
  0xf, , , , , , , ,
  0xa, , , , , , , ,
  , 0xd, , 0xb, , , , ,
  0xe, , , , , , , ,
  , , , , , , 0xc, 
];

function computeAcwScV2(arg1: string): string {
  // unsbox: 根据置换表重排 const unsboxed = UNSBOX_TABLE.map(i => arg1[i - 1]).join('');

  // hexXor: 与 key 异或 let result = '';
  for (let i = 0; i < 40; i += 2) {
    const xored = (
      parseInt(unsboxed.slice(i, i + 2), 16) ^
      parseInt(XOR_KEY.slice(i, i + 2), 16)
    ).toString(16);
    result += xored.padStart(2, '0');
  }

  return result;
}

// 使用 const arg1 = ;
const cookie = computeAcwScV2(arg1);
 

在 Cloudflare Worker 中使用

由于算法是纯计算,不需要 eval,可以直接在 Cloudflare Worker 中运行:

async function getAcwCookie(targetUrl: URL): Promise<string | null> {
  const resp = await fetch(targetUrl.toString(), { redirect: 'manual' });
  const html = await resp.text();

  // 提取 arg1 const match = html.match(/var\s+arg1\s*=\s*'([0-9a-fA-F]+)'/);
  if (!match) return null;

  const cookie = computeAcwScV2(match[1]);
  return `acw_sc__v2=${cookie}`;
}

Worker 代码

直接往 Cloudflare 里一粘就完事了。不过这里也给佬们部署了一个现成的 Worker:https://anyrouter.devip.ip-ddns.com,佬们可以直接用。但如果爆了额度我可能会删掉

const UPSTREAM = 'https://anyrouter.top';
const XOR_KEY = ;
const UNSBOX_TABLE = [0xf, , , , , , , , 0xa, , , , , , , , , 0xd, , 0xb, , , , , 0xe, , , , , , , , , , , , , , 0xc, ];

export default {
  async fetch(req: Request): Promise<Response> {
    const url = new URL(req.url);
    if (url.pathname === '/') return new Response('OK');

    const targetUrl = new URL(url.pathname + url.search, UPSTREAM);
    const cookie = await getAcwCookie(targetUrl);
    if (!cookie) return new Response('Failed to obtain cookie', { status: 502 });

    const headers = new Headers(req.headers);
    headers.set('cookie', [cookie, req.headers.get('cookie')].filter(Boolean).join('; '));
    headers.set('origin', UPSTREAM);
    headers.set('referer', `${UPSTREAM}/`);
    headers.set('host', new URL(UPSTREAM).host);
    headers.delete('content-length');

    const init: RequestInit = { method: req.method, headers, redirect: 'manual' };
    if (!['GET', 'HEAD'].includes(req.method)) init.body = await req.arrayBuffer();

    const resp = await fetch(targetUrl.toString(), init);
    return new Response(resp.body, { status: resp.status, headers: resp.headers });
  },
};

async function getAcwCookie(targetUrl: URL): Promise<string | null> {
  try {
    const resp = await fetch(targetUrl.toString(), {
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      },
      redirect: 'manual',
    });
    const html = await resp.text();
    const match = html.match(/var\s+arg1\s*=\s*'([0-9a-fA-F]+)'/);
    if (!match) return null;

    // unsbox: 根据置换表重排 const unsboxed = UNSBOX_TABLE.map(i => match[1][i - 1]).join('');
    // hexXor: 与 key 异或 let result = '';
    for (let i = 0; i < 40; i += 2) {
      const xored = (parseInt(unsboxed.slice(i, i + 2), 16) ^ parseInt(XOR_KEY.slice(i, i + 2), 16)).toString(16);
      result += xored.padStart(2, '0');
    }
    return `acw_sc__v2=${result}`;
  } catch {
    return null;
  }
}

关于签到脚本

部署到 Cloudflare Worker 后,把所有对 Anyrouter 的请求全部替换成该 Worker 地址,即可。自动签到脚本需要 Cookie,目测有效期为一个月

站内已经有很多签到脚本了,佬们可以任意选择。如果后面有空我可能会写一个,但是感觉重复造轮子意义不大。

服务器
服务器 + 通知
Worker
二合一

参考资料

搞一个 AnyRouter 动态 cookie 验证(内含签到 + cc switch 查询余额) - 开发调优 - LINUX DO
雪球 JS 逆向:阿里系加密 acw_sc__v2 和 反 debugger_ 雪球网逆向 - CSDN 博客


📌 转载信息
转载时间:
2026/1/14 10:48:18