猿人学第9题「js混淆 - 动态cookie 2」解题记录
题目链接:https://match.yuanrenxue.cn/match/9
答案(本人账号):26111363
日期:2026-11-07
一、题目目标与规则
- 请求本页全部 5 页 数据,计算这 50 个值的 加和,提交答案。
- 第 5 页发送请求时必须把
userAgent改成yuanrenxue,否则不返回最后一页数据。 - 发送请求时必须携带
sessionid;每个已登录用户的数字答案都不同。
二、请求分析
数据接口:
GET https://match.yuanrenxue.cn/api/question/9?page=N&pageSize=10&kw=
请求头里关键的两样东西:
| 名称 | 说明 |
|---|---|
sessionid | 登录态,直接决定返回的是“谁的”数据 |
m | 动态生成的 cookie,有时效,验证通过才返回明文数组 |
行为:
- 首次请求(不带
m)返回的data是一段混淆 JS 字符串; - 带有效
m再请求,data才是明文数组。
三、逆向思路
1. hook cookie 定位 m 的生成
在浏览器里 hook document.cookie 的 setter,找到种 m 的位置,还原出易读代码:
window = new Array();
for (var m = 0x1; m <= N; m++) {
res = decrypt(ts) + '\x72'; // '\x72' 就是字符 'r'
}
document['cookie'] = 'm=' + (m - 0x1)['toString']() + res + '; path=/';
即:
m = N + decrypt(ts) + 'r'
其中:
N:循环上界,从返回的混淆 JS 里正则提取;ts:响应里内嵌的 10 位时间戳。
2. decrypt = RSA 加密
还原 decrypt:
function decrypt(text) {
const pub = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5GVku07yXCndaMS1evPIPyWwhbdWMVRqL4qg4OsKbzyTGmV4YkG8H0hwwrFLuPhqC5tL136aaizuL/lN5DRRbePct6syILOLLCBJ5J5rQyGr00l1zQvdNKYp4tT5EFlqw8tlPkibcsd5Ecc8sTYa77HxNeIa6DRuObC5H9t85ALJyDVZC3Y4ES/u61Q7LDnB3kG9MnXJsJiQxm1pLkE7Zfxy29d5JaXbbfwhCDSjE4+dUQoq2MVIt2qVjZSo5Hd/bAFGU1Lmc7GkFeLiLjNTOfECF52ms/dks92Wx/glfRuK4h/fcxtGB4Q2VXu5k68e/2uojs6jnFsMKVe+FVUDkQIDAQAB';
const e = new JSEncrypt();
e.setPublicKey(pub);
return encodeURIComponent(e.encrypt(text));
}
即 decrypt 本质是 2048 位 RSA 公钥加密 + encodeURIComponent,加密对象就是时间戳 ts。
3. 踩坑点
m有时效,生成后必须立即请求,否则过期又返回混淆 JS。m的前缀是循环上界N(不用减 1)。udc.js是jsjiami.com.v5混淆:字符串数组 + RC4 解码、大数组顺序混淆、atob/window.crypto环境检测、无限 debugger。- 手写 RSA 容易踩环境坑(crypto 检测、随机填充等),最稳的办法是让浏览器自己算
m。
四、最终代码
思路:不手写 RSA,用 Playwright 打开题目页,让浏览器自己生成 m,再把 sessionid + m 交给 requests 拉 5 页。
# -*- coding: utf-8 -*-
"""
猿人学 第9题「js混淆 - 动态cookie 2」爬虫
用法:
python solve_crawl.py # 匿名会话
python solve_crawl.py --sessionid <你的sessionid> # 你账号的答案
python solve_crawl.py --cookie "sessionid=xxx; ..." # 粘贴整段 Cookie
"""
import asyncio
import argparse
import sys
import requests
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
from playwright.async_api import async_playwright
URL = "https://match.yuanrenxue.cn"
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36"
BASE_HEADERS = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"referer": URL + "/match/9",
"x-requested-with": "XMLHttpRequest",
"user-agent": UA,
}
def parse_cookie(cookie_str):
if not cookie_str:
return None
for part in cookie_str.split(";"):
part = part.strip()
if part.lower().startswith("sessionid="):
return part.split("=", 1)[1]
return None
async def get_session_and_m(sessionid):
async with async_playwright() as p:
browser = await p.chromium.launch(channel="chrome", headless=True, args=["--no-sandbox"])
context = await browser.new_context(user_agent=UA)
if sessionid:
await context.add_cookies([{
"name": "sessionid",
"value": sessionid,
"domain": ".match.yuanrenxue.cn",
"path": "/",
}])
page = await context.new_page()
await page.goto(URL + "/match/9", wait_until="networkidle", timeout=60000)
m = None
for _ in range(80):
cookies = await context.cookies()
m = next((c["value"] for c in cookies if c["name"] == "m"), None)
if m:
break
await page.wait_for_timeout(250)
cookies = await context.cookies()
sid = next((c["value"] for c in cookies if c["name"] == "sessionid"), sessionid or "")
await browser.close()
if not m:
raise RuntimeError("未能生成 m cookie")
return sid, m
def fetch_and_sum(sessionid, m):
s = requests.Session()
s.headers.update(BASE_HEADERS)
s.cookies.set("sessionid", sessionid, domain="match.yuanrenxue.cn")
s.cookies.set("m", m, domain="match.yuanrenxue.cn")
total = 0
per_page = {}
for page in range(1, 6):
headers = dict(BASE_HEADERS)
if page == 5:
headers["user-agent"] = "yuanrenxue"
r = s.get(
URL + "/api/question/9",
params={"page": page, "pageSize": 10, "kw": ""},
headers=headers,
timeout=20,
)
data = r.json().get("data")
if not isinstance(data, list):
raise RuntimeError(f"第 {page} 页未拿到数组,m 可能已过期")
nums = [int(x) for x in data]
per_page[page] = nums
total += sum(nums)
print(f"第 {page} 页: {nums} 小计={sum(nums)}")
print("-" * 56)
for page in range(1, 6):
print(f" 第{page}页 {per_page[page]}")
print("-" * 56)
print("答案(总和) =", total)
return total
def main():
ap = argparse.ArgumentParser(description="猿人学第9题爬虫")
ap.add_argument("--sessionid", default=None, help="你已登录的 sessionid")
ap.add_argument("--cookie", default=None, help="浏览器整段 Cookie 字符串")
args = ap.parse_args()
sessionid = args.sessionid or parse_cookie(args.cookie)
if args.cookie and not sessionid:
print("警告: 未能从 --cookie 中解析出 sessionid,将以匿名会话运行")
if sessionid:
chk = requests.Session()
chk.headers.update({"user-agent": UA})
chk.cookies.set("sessionid", sessionid, domain="match.yuanrenxue.cn")
try:
login = bool(chk.get(URL + "/api/user", timeout=15).json().get("isLogin"))
except Exception:
login = False
if not login:
print(f"警告: 该 sessionid 已失效或未登录: {sessionid}")
print(" 请重新登录后复制新的 sessionid,本次以匿名会话运行")
sessionid = None
sessionid, m = asyncio.run(get_session_and_m(sessionid))
print("sessionid =", sessionid)
print("m =", m[:60] + "...")
fetch_and_sum(sessionid, m)
if __name__ == "__main__":
main()
运行:
python solve_crawl.py --sessionid a7tk93g4qzjf2wzjxohbnj75cix2qhyh
五、运行结果
sessionid = a7tk93g4qzjf2wzjxohbnj75cix2qhyh
第 1 页: [845342, 478914, 625140, 357765, 505428, 395262, 434411, 355931, 721870, 223326] 小计=4943389
第 2 页: [952507, 577739, 149953, 276184, 722346, 849033, 550283, 141163, 352705, 347246] 小计=4919159
第 3 页: [283936, 283389, 610606, 500036, 130340, 249192, 795179, 469437, 587619, 597259] 小计=4506993
第 4 页: [897610, 314521, 960760, 219332, 618791, 692427, 920053, 696008, 903900, 513455] 小计=6736857
第 5 页: [111810, 226174, 469122, 634180, 781892, 726554, 402923, 478109, 577089, 597112] 小计=5004965
--------------------------------------------------------
答案(总和) = 26111363
六、总结
- 第 9 题的核心是
mcookie:m = N + RSA(ts) + 'r'。 udc.js混淆重、环境检测多,手写 RSA 容易翻车。- 用 Playwright 让浏览器替我们生成
m,再用requests快速抓 5 页,是最稳的组合。 - 别忘了两件事:第 5 页 UA 改成
yuanrenxue、用自己登录的sessionid(每个账号答案不同)。
完结撒花 ✿✿ヽ(°▽°)ノ✿
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2501_91134400/article/details/170762122



