绕过 Anyrouter 的动态 Cookie (acw_sc__v2) 验证【支持 Worker 部署】
众所周知,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,目测有效期为一个月。
站内已经有很多签到脚本了,佬们可以任意选择。如果后面有空我可能会写一个,但是感觉重复造轮子意义不大。
参考资料
搞一个 AnyRouter 动态 cookie 验证(内含签到 + cc switch 查询余额) - 开发调优 - LINUX DO
雪球 JS 逆向:阿里系加密 acw_sc__v2 和 反 debugger_ 雪球网逆向 - CSDN 博客








