当前位置: 首页 > news >正文

JS逆向|猿人学逆向反混淆练习平台第10题加密分析

关注它,不迷路。

  • 本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!

一.题目地址

https://match.yuanrenxue.cn/match/10

二.抓包分析

先打开控制台,然后再打开上面的网站,停在debugger位置:

这个debugger我没办法绕过,那用小黄鸟来抓包看看数据在哪个接口:

可以看到,在下面这个接口:

https://match.yuanrenxue.cn/api/question/10?page=1&m=4UrkgIwjsrchfAKOC2qRltb4jx0ieHyhXvZItorSg8hL5lGtjcIt_woJEPyvWewABEIiJjuvPYIOUv86BABZmAn1OFfKMmCLYbSwjmR7UFfKMmCYmUqTEnBp7oflQSBmmAt9v_wfXtU.aXVXKEKJWcBX0ao7Vw4lKRs2xYzBxzh6gaGdSNhESMxMQcwh7YnbDgP9J6MWh.I8DFZLPQXR9wNa.3lqeMXoi.yx3UD6pUqABnehUOPUpl0u1KP81jwHuK.37LygGdVuNAn47UB_NGoEQ

这个 m 参数就是需要逆向的参数。

三.还原混淆代码

回到浏览器解密,往上追堆栈,抠取整个混淆js:

保存为 m.js,然后使用星球里的美化插件美化一下:

https://t.zsxq.com/7H8rq

分析美化后的代码,看到有一些函数调用:

这个是类ob混淆,可以使用星球里的万能字符串还原框架 来还原:

https://t.zsxq.com/ATz1S

字符串解密替换后,继续用ob混淆后处理插件,结果文件保存为step1.js

https://t.zsxq.com/xH2v7

这一步过后,我们就去掉了ob混淆的壳。它还有一层,是早期的某数加密。

这个时候,手动来还原处理,有点吃力,我们使用AI来做还原处理。

AI提示词:

step1.js是一段混消的js,请你用你最强的反混淆技能将里面的while-if语句转换为while-switch新句,不要借助在何skills,本地已安装babel库。提示,你可以使用夹通原理来还原,如 yuanrenxue_105<2 其实就是yuanrenxue_105 = 1

这一步完成后,继续让AI还原:

请你继续还原,去控制流,注意下标索引数组yuanrenxue_53,根据它的内容去控制流即可

这一步完成后,继续让AI还原:

请你根据最新生成的文件,提取核心算法,注意需要用你最强的逆向分析技能。

AI给我了一个核心的算法(core_algorithm_extracted.js):

const fs = require('fs');const path = require('path');/** * Extracted core from step1.while-switch.js / step1.decontrol.js. * It contains: * 1) VM integer reader used by the dfe1675 bytecode/code-generator payload. * 2) Name/string pool splitter used by yuanrenxue_31(10/16). * 3) dfe1683 second-stage payload decoder. * 4) yuanrenxue_34 / $_ts.yuanrenxue_30 state transform, simplified. */function readEncodedNumber(s, state) { let c = s.charCodeAt(state.i++); let t; const norm = x => x >= 128 ? x - 32 : x; if (c < 128) return c; if (c < 251) return c - 32; if (c === 251) return 0; if (c === 254) { c = norm(s.charCodeAt(state.i++)); t = norm(s.charCodeAt(state.i++)); return c * 219 + t; } if (c === 255) { c = norm(s.charCodeAt(state.i++)); t = norm(s.charCodeAt(state.i++)); c = c * 219 * 219 + t * 219; t = norm(s.charCodeAt(state.i++)); return c + t; } if (c === 252) { t = norm(s.charCodeAt(state.i++)); return -t; } if (c === 253) { c = norm(s.charCodeAt(state.i++)); t = norm(s.charCodeAt(state.i++)); return c * -219 - t; } throw new Error('bad encoded number tag: ' + c);}function splitNamePool(raw) { const out = new Array(Math.floor(raw.length / 2)); let j = 0; for (let i = 0; i < raw.length; i += 2) out[j++] = '_$' + raw.substr(i, 2); return out;}function decodeDfe1683(dfe1683) { // dfe1683 can be an array-like of one-char strings or a string. let b64 = ''; for (let i = 0; i < dfe1683.length; i++) { b64 += String.fromCharCode(String(dfe1683[i]).charCodeAt(0) - i % 991 - 50); } if (typeof atob === 'function') return atob(b64); return Buffer.from(b64, 'base64').toString('binary');}function encodeDfe1683ForTest(plainJs) { const b64 = Buffer.from(plainJs, 'binary').toString('base64'); const out = []; for (let i = 0; i < b64.length; i++) { out.push(String.fromCharCode(b64.charCodeAt(i) + i % 991 + 50)); } return out;}function coreStateTransform(input) { // Exact semantic reduction of original yuanrenxue_34 / $_ts.yuanrenxue_30. // The input array is mutated in-place. Original input values are not used in // the final state; the transform is a constant VM-state/key schedule reset. if (!Array.isArray(input) || input.length < 16) { throw new TypeError('coreStateTransform expects an array with at least 16 slots'); } input[0] = 6; input[1] = 7; input[2] = 0; input[3] = 9; input[4] = 2; input[5] = 11; input[6] = 16; input[7] = 13; input[8] = 6; input[9] = 15; input[10] = -1; input[11] = 1; input[12] = 10; input[13] = 3; input[14] = 12; input[15] = 5; return 7;}const YUANRENXUE_31_16_RAW = [ 'EkO28VUyeb_do5BcpMOakpeRKwYbsTRbBnvjN9qOtlHGk0xrr3p8hl1rAXoejZtZ92cBJ6o0X5ftIVCYeeATJ$J3DgCTMkPJajy', 'uqUEztn8_zN_XKgHqJlaUuI4evIk7I5RBMOz2LQW3GZcuv0HJ2l7tVqtqS1wj5A5_B2JJC0iIph6JJOUsQfuwaQGTb_kGGdlLL_', 'mPit96G9kh6Ne4p4MFU2Nrm2DU2sOdiRSCjFHvsPDSkz_ISFTH$_2JtxC_Vbo2j_A8qS7ZRykinZiJgaIqUTCfJjPmH_y3xqlDM', 'pbUPsFUZzy9j15yKB2NB3CBluae9NfIg1qMeA_8heQuQKi$AanXNMCAMXEnJCyQCK6vLzDzvxvtc_OYOStwenw$mjbuJuPqcml5', 'Q64lAISShUnriY7_kCaH1wEF8uGonfQGNbMdv8153a$t8CMvDDeMS63Qz0hOZpjRvdc6rN6TbMySbPJbSHfemTvJkWaVIuI$VCX', 'ki3LbQyO$tUPKa4qnVnNuDMtTM6QCOJtEvZqV0Zk6tG_JH8KC3S7T56smwFGUmDGzuaN5FyDPHc2K_LV7oYoGi1kSD6StODkKyo', 'j2pI8ezB6LGlfwnwn7GksRCH4YnU8a0d66g5ZPHImfYkqFCJ4PfYOcUlMw_WW1_inDkyeheWvseB2Os$JpwsUuaK38KAjcY2WmY', 'f0yuq7ROTouDf48F9xewcMr22ri0P3UBVbKQtI9VgS9pgybcUi2Bd89EDbZgBGiGtnaO9aCJfUzO8H1u93j8czJ369ul26QE2Kh', 'xE_9_SMIDIpaNerAzzn8blWzJDhhvehcC9nSbemlqEuTBHsXkM8KLT4rz6XnFie0LRJQXOkq$cDNnyuanrenxue_55', 'C9mp0MaWii4R9AzqKyZSv6heX7YpCAxHm3JZQ3sjTBNDXRp1zyE_K5ztAL70YBA6bm9gD8GAuTl5LXYpd0wVvutQHL$FVRjsyT$z8bCCzwM'].join('').replace(/[\r\n\s]/g, '');function getBootstrapNamePool() { return splitNamePool(YUANRENXUE_31_16_RAW);}module.exports = { readEncodedNumber, splitNamePool, decodeDfe1683, encodeDfe1683ForTest, coreStateTransform, getBootstrapNamePool,};if (require.main === module) { const a = Array.from({ length: 16 }, (_, i) => 100 + i); const ret = coreStateTransform(a); console.log('[coreStateTransform] ret =', ret, 'state =', JSON.stringify(a)); const pool = getBootstrapNamePool(); console.log('[namePool] len =', pool.length, 'sample =', pool.slice(0, 8)); const sample = 'console.log("hello-stage2")'; const enc = encodeDfe1683ForTest(sample); console.log('[dfe1683] roundtrip =', decodeDfe1683(enc));}

提取算法后,还不够,还需要在线调试逆向,继续让AI操作。

四.AI逆向提示词

使用:JSReverse MCP方式:插桩采集完整的输入输出及中间态数据,与本地算法进行逻辑一致性和结果正确性对比分析。URL: https://match.yuanrenxue.cn/match/10目标:【目标接口,https://match.yuanrenxue.cn/api/question/10 这个接口会带上page和m参数,需要你逆向m参数的加密方式】 触发方式: 【翻面】 约束:不使用playwright等浏览器自动化工具,不能联网搜索公开案例 cookie:将{"sessionid":"换成你的sessionid"}加入到请求代码中,表示当前登录UA设置:yuanrenxue交付:可运行的python脚本,运行后打印1-5页的响应数据,并计算总和辅助:当前还原的step1_decontrol.js是其核心js,而core_algorithm_extracted.js是你提取的核心算法,你可以用它来辅助分析

大概半个小时,它就给了纯算的代码:

# -*- coding: utf-8 -*-"""Yuanrenxue match/10 solver.No browser automation / no Playwright.- suffix64: pure Python implementation of custom SHA1 + AES-like-CBC + custom base64.- prefix217: runs only the extracted stage-2 VM function aiding_5702 in Node with a minimal context (Math/Date/String/Array/etc.); no DOM/document/navigator/XHR/browser???."""import base64import jsonimport reimport subprocessimport tempfilefrom pathlib import Pathfrom urllib.parse import urlsplit import requests BASE = 'https://match.yuanrenxue.cn'COOKIE = '' #sessionidUA = 'yuanrenxue'FIXED_IV = 0x1F9ADD37 CUSTOM_ALPHABET = "qrcklmDoExthWJiHAp1sVYKU3RFMQw8IGfPO92bvLNj.7zXBaSnu0TC6gy_4Ze5d{}|~ !#$%()*+,-;=?@[]^" def u32(x): return x & 0xffffffff def i32(x): x &= 0xffffffff return x - 0x100000000 if x >= 0x80000000 else x def js_num(x): return 0 if x is None else x def js_xor(*vals): r=0 for v in vals: r = i32(u32(r) ^ u32(js_num(v))) return r def rotl(x,n): return i32(((u32(x)<<n)|(u32(x)>>(32-n))) & 0xffffffff) def zf_rshift(x,n): return (u32(x)>>n) def sv_words(bs): # JS _yrxSVn/_yrxOFj bug: always starts with Array(16), fills while index < len, missing bytes bitwise=>0 out=[None]*16 i=j=0; L=len(bs) while i<L: b0=bs[i] if i<L else 0; i+=1 b1=bs[i] if i<L else 0; i+=1 b2=bs[i] if i<L else 0; i+=1 b3=bs[i] if i<L else 0; i+=1 out[j]=i32((b0<<24)|(b1<<16)|(b2<<8)|b3); j+=1 return out def words_to_bytes(words): out=[] for w in words: w=u32(js_num(w)) out += [(w>>24)&255,(w>>16)&255,(w>>8)&255,w&255] return out def bytes_of(x): if isinstance(x, str): return list(x.encode('utf-8')) return [int(v)&255 for v in x] def sha_bug(data): st=[0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xc3d2e1f0] st=[i32(x) for x in st] K=[1518500249,1859775393,2400959708,3395469782] buf=bytes_of(data); total=0 def compress(W): nonlocal st W=W[:] a,b,c,d,e=st for t in range(80): if t>=16: tmp=js_xor(W[t-3],W[t-8],W[t-14],W[t-16]) W.append(rotl(tmp,1)) if t>=len(W) else W.__setitem__(t, rotl(tmp,1)) aa=rotl(a,5) if t<=19: f=i32((u32(b)&u32(c)) | ((~u32(b))&u32(d))) elif t<=39: f=js_xor(b,c,d) elif t<=59: f=i32((u32(b)&u32(c)) | (u32(b)&u32(d)) | (u32(c)&u32(d))) else: f=js_xor(b,c,d) if t < 16 and (t>=len(W) or W[t] is None): temp=0 else: temp=i32(aa + f + e + js_num(W[t]) + K[t//20]) e=d; d=c; c=rotl(b,30); b=a; a=temp st=[i32(st[0]+a), i32(st[1]+b), i32(st[2]+c), i32(st[3]+d), i32(st[4]+e)] total += len(buf) while len(buf)>=64: block=buf[:64]; del buf[:64] compress(sv_words(block)) buf.append(0x80) r=len(buf)+8 while r & 0x3f: buf.append(0); r+=1 while len(buf)>=64: block=buf[:64]; del buf[:64] compress(sv_words(block)) W=sv_words(buf) W.append((total*8)//0x100000000) W.append(i32(total*8)) compress(W) return words_to_bytes(st) def wkg(*args): h=None # only used single arg; use class-like update for multiple st=[0x67452301,0xefcdab89,0x98badcfe,0x10325476,0xC3D2E1F0] # easier: concatenate args? original incremental should same with bug if no full block; all args single. data=[] for a in args: data += bytes_of(a) return sha_bug(data)[:16] # custom b64 decode/encodemaps={}for i,ch in enumerate(CUSTOM_ALPHABET): c=ord(ch); maps[c]=i def arrval(c, typ): if c is None: idx=0 else: idx=maps.get(ord(c) if isinstance(c,str) else c, 0) if typ==0: return idx<<2 if typ==1: return idx>>4 if typ==2: return (idx&15)<<4 if typ==3: return idx>>2 if typ==4: return (idx&3)<<6 if typ==5: return idx def b64_dec(s): n=len(s); out=[]; i=0; end=n-3 while i<end: c1=s[i]; i+=1; c2=s[i]; i+=1; c3=s[i]; i+=1; c4=s[i]; i+=1 out.append((arrval(c1,0)|arrval(c2,1))&255) out.append((arrval(c2,2)|arrval(c3,3))&255) out.append((arrval(c3,4)|arrval(c4,5))&255) if i<n: c1=s[i]; i+=1; c2=s[i] if i<n else None; i+=1 out.append((arrval(c1,0)|arrval(c2,1))&255) if i<n: c3=s[i] out.append((arrval(c2,2)|arrval(c3,3))&255) return out def b64_enc(bs, alphabet=CUSTOM_ALPHABET): out=[]; i=0; L=len(bs); end=L-2 while i<end: b0=bs[i]; i+=1; out.append(alphabet[b0>>2]) b1=bs[i]; i+=1; out.append(alphabet[((b0&3)<<4)|(b1>>4)]) b2=bs[i]; i+=1; out.append(alphabet[((b1&15)<<2)|(b2>>6)]); out.append(alphabet[b2&63]) if i<L: b0=bs[i]; out.append(alphabet[b0>>2]); i+=1 b1=bs[i] if i<L else None out.append(alphabet[((b0&3)<<4)|((b1 or 0)>>4)]) if b1 is not None: out.append(alphabet[(b1&15)<<2]) return ''.join(out) # AES-ishSBOX=None; INV=None; TENC=None; TDEC=None def init_tables(): global SBOX,INV,TENC,TDEC if SBOX is not None: return enc=[[0]*256 for _ in range(5)]; dec=[[0]*256 for _ in range(5)] rq=enc[4]; inv=dec[4]; tx=[0]*256; xx=[0]*256 for i in range(256): tx[i]=i<<1 ^ ((i>>7)*283) tx[i]&=0xffffffff xx[tx[i]^i]=i x=y=0 while not rq[x]: g=y ^ (y<<1) ^ (y<<2) ^ (y<<3) ^ (y<<4) g=((g>>8) ^ (g&255) ^ 99) & 255 rq[x]=g; inv[g]=x usw=tx[x] x = x ^ (usw or 1) y = xx[y] or 1 for i in range(256): inv[rq[i]]=i for x in range(256): g=rq[x] usw=tx[x]; wfm=tx[usw]; ea=tx[wfm] im=i32((ea*0x1010101) ^ (wfm*0x10001) ^ (usw*0x101) ^ (x*0x1010100)) sf=i32(tx[g]*0x101 ^ g*0x1010100) for j in range(4): sf=i32(((u32(sf)<<24)&0xffffffff) ^ zf_rshift(sf,8)); enc[j][x]=sf im=i32(((u32(im)<<24)&0xffffffff) ^ zf_rshift(im,8)); dec[j][g]=im for j in range(5): enc[j]=enc[j][:]; dec[j]=dec[j][:] SBOX=rq; INV=inv; TENC=enc; TDEC=dec def key_schedule(key_bytes): init_tables() W=sv_words(key_bytes) wfm=len(W); rcon=1; encW=W[:] # JS loop reads/writes up to 4*wfm+27 inclusive? for(i=wfm;i<4*wfm+28;i++) for i in range(wfm, 4*wfm+28): v=js_num(encW[i-1]) if i%wfm==0 or (wfm==8 and i%wfm==4): v=i32((SBOX[zf_rshift(v,24)]<<24) ^ (SBOX[(v>>16)&255]<<16) ^ (SBOX[(v>>8)&255]<<8) ^ SBOX[v&255]) if i%wfm==0: v=i32(((u32(v)<<8)&0xffffffff) ^ zf_rshift(v,24) ^ (rcon<<24)) rcon=i32((rcon<<1) ^ ((rcon>>7)*283)) encW.append(i32(js_num(encW[i-wfm]) ^ u32(v))) decW=[] i=len(encW) j=0 while i: src = i if (j & 3) else i-4 v=js_num(encW[src]) if i<=4 or j<4: decW.append(v) else: decW.append(i32(TDEC[0][SBOX[zf_rshift(v,24)]] ^ TDEC[1][SBOX[(v>>16)&255]] ^ TDEC[2][SBOX[(v>>8)&255]] ^ TDEC[3][SBOX[v&255]])) j+=1; i-=1 return encW,decW def encrypt_block(schedule, block): encW,_=schedule; tbl=TENC a=i32(js_num(block[0]) ^ js_num(encW[0])); b=i32(js_num(block[1]) ^ js_num(encW[1])); c=i32(js_num(block[2]) ^ js_num(encW[2])); d=i32(js_num(block[3]) ^ js_num(encW[3])) rounds=len(encW)//4 - 2; idx=4 for _ in range(rounds): aa=i32(tbl[0][zf_rshift(a,24)] ^ tbl[1][(b>>16)&255] ^ tbl[2][(c>>8)&255] ^ tbl[3][d&255] ^ js_num(encW[idx])); bb=i32(tbl[0][zf_rshift(b,24)] ^ tbl[1][(c>>16)&255] ^ tbl[2][(d>>8)&255] ^ tbl[3][a&255] ^ js_num(encW[idx+1])); cc=i32(tbl[0][zf_rshift(c,24)] ^ tbl[1][(d>>16)&255] ^ tbl[2][(a>>8)&255] ^ tbl[3][b&255] ^ js_num(encW[idx+2])); d =i32(tbl[0][zf_rshift(d,24)] ^ tbl[1][(a>>16)&255] ^ tbl[2][(b>>8)&255] ^ tbl[3][c&255] ^ js_num(encW[idx+3])); idx+=4; a,b,c=aa,bb,cc out=[0,0,0,0] vals=[a,b,c,d] for j in range(4): # _yrx4Sf[_yrxyqC?3&-j:j] for encrypt -> j out[j]=i32((SBOX[zf_rshift(vals[0],24)]<<24) ^ (SBOX[(vals[1]>>16)&255]<<16) ^ (SBOX[(vals[2]>>8)&255]<<8) ^ SBOX[vals[3]&255] ^ js_num(encW[idx])); idx+=1 vals=[vals[1],vals[2],vals[3],vals[0]] return out def aesish_cbc_encrypt(payload, key_bytes, iv_words): sched=key_schedule(key_bytes) nblocks=len(payload)//16 + 1 pad=16 - (len(payload)%16) data=payload[:] + [pad]*pad words=sv_words(data) prev=iv_words[:] out=iv_words[:] for bi in range(nblocks): block=words[bi*4:(bi+1)*4] block=[i32(js_num(block[k]) ^ js_num(prev[k])) for k in range(4)] prev=encrypt_block(sched, block) out += prev return words_to_bytes(out) def suffix_for_payload(payload, iv_word=0x1F9ADD37): key=b64_dec('EgWWundefined') enc=aesish_cbc_encrypt(payload, key, [i32(iv_word)]*4) return b64_enc(enc), enc PREFIX_HELPER = 'const fs=require(\'fs\'), vm=require(\'vm\');\nconst input=JSON.parse(fs.readFileSync(process.argv[2],\'utf8\'));\nfunction findFuncEnd(s, name){\n let i=s.indexOf(\'function \'+name); if(i<0) throw new Error(\'no function \'+name);\n let j=s.indexOf(\'{\',i), d=0, ins=null, esc=false;\n for(let k=j;k<s.length;k++){\n let c=s[k];\n if(ins){ if(esc)esc=false; else if(c===\'\\\\\')esc=true; else if(c===ins)ins=null; }\n else { if(c===\'"\'||c==="\'"||c===\'`\')ins=c; else if(c===\'{\')d++; else if(c===\'}\'){ d--; if(d===0)return k+1; } }\n }\n throw new Error(\'no end for \'+name);\n}\nfunction extractVml(full, mindex){\n const start=full.indexOf(\'_yrxVMl=\',mindex)+\'_yrxVMl=\'.length;\n let d=0, ins=null, esc=false;\n for(let i=start;i<full.length;i++){\n let c=full[i];\n if(ins){ if(esc)esc=false; else if(c===\'\\\\\')esc=true; else if(c===ins)ins=null; }\n else { if(c===\'"\'||c==="\'"||c===\'`\')ins=c; else if(c===\'{\')d++; else if(c===\'}\'){ d--; if(d===0)return full.slice(start,i+1); } }\n }\n throw new Error(\'no _yrxVMl object\');\n}\nconst full=fs.readFileSync(input.decodedPath,\'utf8\');\nconst aidEnd=findFuncEnd(full,\'aiding_5702\');\nconst core=full.slice(0,aidEnd);\nconst m=full.match(/_yrxays=\'([^\']*)\';_yrxVMl=/); if(!m) throw new Error(\'no _yrxays/_yrxVMl\');\nconst ays=m[1], vml=extractVml(full,m.index);\nconst wrapper=`var window=this; this.window=this; this.Math=Object.create(Math); this.Date=Date; ${core}; window.__go=function(off){window[${JSON.stringify(input.offsetName)}]=Number(off)||0; return aiding_5702(${JSON.stringify(ays)}, ${vml}, \'_yrxdD_\', [203,103,203,618]);};})();`;\nconst ctx={console, Math, Date, parseInt, unescape, encodeURIComponent, Function, String, Array, Error, Object, RegExp, Number, JSON};\nctx.window=ctx;\nvm.runInNewContext(wrapper,ctx,{timeout:5000});\nconsole.log(ctx.__go(input.offsetValue));' def extract_offset(txt): m = re.search(r'window\.([A-Za-z_$][\w$]*)\s*=\s*(\d+)', txt) if not m: raise RuntimeError('bad offset response: ' + txt[:200]) return m.group(1), int(m.group(2)) def decode_api2(js, mod): m = re.search(r"\$_ts\['dfe1683'\]='([\s\S]*)';?\s*$", js) if not m: raise RuntimeError('dfe1683 not found in api2/10') enc = m.group(1) b64 = ''.join(chr(ord(ch) - i % mod - 50) for i, ch in enumerate(enc)) return base64.b64decode(b64).decode('utf-8') def suffix_for_url(raw_url): u = urlsplit(raw_url) canonical = (u.path + (('?' + u.query) if u.query else '')).upper() url_hash16 = wkg(canonical) salt8 = wkg(b64_dec('undefined'))[:8] payload = url_hash16 + [0, 0] + salt8 suffix, _ = suffix_for_payload(payload, FIXED_IV) return suffix def prefix_from_decoded(decoded_path, helper_path, offset_name, offset_value): inp = { 'decodedPath': str(decoded_path), 'offsetName': offset_name, 'offsetValue': int(offset_value), } input_path = decoded_path.with_suffix('.prefix_input.json') input_path.write_text(json.dumps(inp, ensure_ascii=False), encoding='utf8') return subprocess.check_output( ['node', str(helper_path), str(input_path)], cwd=str(decoded_path.parent), text=True, timeout=20, ).strip() def sign_url(raw_url, decoded_path, helper_path, offset_name, offset_value): prefix = prefix_from_decoded(decoded_path, helper_path, offset_name, offset_value) m = prefix + suffix_for_url(raw_url) sep = '&' if '?' in raw_url else '?' return raw_url + sep + 'm=' + m, m def main(): sess = requests.Session() sess.headers.update({ 'User-Agent': UA, 'Referer': BASE + '/match/10', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01', }) sess.cookies.set('sessionid', COOKIE, domain='match.yuanrenxue.cn', path='/') off_txt = sess.get(BASE + '/api/10/offset', timeout=20).text offset_name, offset_value = extract_offset(off_txt) api2 = sess.get(BASE + '/api2/10', timeout=30).text decoded = decode_api2(api2, offset_value) with tempfile.TemporaryDirectory() as td0: td = Path(td0) decoded_path = td / 'api2_10.decoded.js' helper_path = td / 'prefix_helper.js' decoded_path.write_text(decoded, encoding='utf8') helper_path.write_text(PREFIX_HELPER, encoding='utf8') print(f'[init] offset {offset_name}={offset_value}, decoded_len={len(decoded)}') total = 0 for page in range(1, 6): raw_url = f'{BASE}/api/question/10?page={page}' signed, m_value = sign_url(raw_url, decoded_path, helper_path, offset_name, offset_value) r = sess.get(signed, timeout=20) print(f'[page {page}] m_len={len(m_value)} response={r.text}') r.raise_for_status() data = r.json() total += sum(int(x) for x in data.get('data', [])) k = (data.get('k') or {}).get('k') if k and '|' in k: offset_name, val = k.split('|', 1) offset_value = int(val) print(f'[page {page}] next offset {offset_name}={offset_value}') print('[total]', total) if __name__ == '__main__': main()

今天的分享就到这里,感谢阅读。

建了个 AST反混淆+AI逆向 的 群,想进群的可以私聊我。

http://www.jsqmd.com/news/904621/

相关文章:

  • 内存泄漏排查实战
  • 苏州翡翠回收避坑攻略!2026实测6家门店,远离低价隐形套路 - 薛定谔的梨花猫
  • 多进程爬虫:利用多核CPU分别爬取不同的板块。多进程爬虫实战:利用多核CPU并发爬取多个板块,性能提升500%
  • 555定时器无稳态多谐振荡器:从原理到频率调制的实践指南
  • GRBL-Plotter:从创意到成品的数字制造桥梁
  • GBKtoUTF-8:高效解决中文乱码的终极编码转换工具
  • SpringBoot + RuoYi + 达梦数据库整合实战:一份完整的application.yml配置清单与SQL改写手册
  • 广州小红书代运营公司排名及联系方式——广州市壹起航科技有限公司:17年全网营销积淀,打造小红书实效代运营行业标杆(更新时间:2026-05-27 23:16:59) - 趣谈科技事物
  • 生产者消费者模式:使用Queue标准库构建生产者消费者爬虫模型。深度实战:基于Queue标准库的生产者消费者爬虫模型,打造高并发分布式采集系统
  • 超越基础:为你的Unity小地图加入高级功能(雷达扫描、迷雾探索、多目标标记)
  • Akagi麻将AI助手:你的实时私人教练,让每局麻将都成为学习机会
  • Windows Cleaner:3步彻底告别C盘爆红,让你的电脑飞起来!
  • 系统性能调优实战:JVM与应用优化
  • 激光雕刻控制软件LaserGRBL:从入门到精通的5个关键问题解答
  • Linux内核开发者视角:深入PCIe AER驱动与Firmware First模型的交互与优化
  • 基于Arduino与蓝牙模块的智能小车制作:从硬件组装到代码调试全流程
  • 3分钟快速激活Beyond Compare:终极免费密钥生成方案
  • 如何轻松获取网页媒体:猫抓扩展的实用技巧指南
  • 猫抓:网页视频下载的终极解决方案,轻松捕获所有流媒体资源
  • 告别Win10资源管理器默认文件夹:除了删注册表,还有这几种隐藏/恢复方法
  • 【会议征稿通知 | 中国石油大学(华东)主办 | JPCS出版 | EI 、Scopus稳定检索】第十届矿产资源、岩土与地质勘探国际学术会议 (MRGGE 2026)
  • 布隆过滤器去重:在分布式环境下使用布隆过滤器去重URL。布隆过滤器去重实战:每天处理千万级URL的Python爬虫这样写
  • 通达信缠论插件ChanlunX:从零到精通的完整技术分析指南
  • 从零开始点亮LED:电子入门基础与Tinkercad仿真实践
  • 无细胞蛋白表达应用案例:eProtein Discovery实现BTK抑制剂5天筛选与功能表征
  • 5步构建炉石传说AI机器人:Hearthrock引擎实战指南
  • Scrapy + Redis:使用Scrapy-Redis实现分布式抓取。Scrapy + Redis:从零构建企业级分布式爬虫系统
  • 如何快速部署LAVIS:面向开发者的多模态AI完整实践指南
  • 从医疗诊断到垃圾邮件过滤:混淆矩阵与F1 Score在实际业务场景中的选择指南
  • Excel高手进阶:用MID、FIND和LEN玩转不规则文本拆分(附模板下载)