AI 逆向分析国航 AirChina FECU 参数来源并实现离线生成
2026/4/23 10:29:31 网站建设 项目流程

AI 逆向分析国航 AirChina FECU 参数来源并实现离线生成

1. 背景与目标

本次分析的目标是定位网页请求中FECU参数的来源,并将其从浏览器环境中剥离出来,最终通过 Python 的execjs调用 JS 运行时离线生成。最初直接运行catch.py时虽然可以发起请求,但服务端返回body: error;同时本地生成的FECU长度和网页实时生成结果不一致,因此需要继续追踪FECU的真实生成链路。

用户在浏览器中验证的调用方式如下:

a0_0x411f3a('/g/invoke.json222')

返回形式类似:

/g/invoke.json222?FECU=3logsbxN7vnp5HFjIqafz0N%2Fu%2BQt...

这说明FECU并不是接口固定参数,而是由页面加载的混淆 JS 动态生成并追加到 URL 中。

2. 定位核心函数:a0_0x411f3aa0_0x56ab93

通过分析提取出的 JS 文件hxk_fec_bc29daa3.raw.js和格式化后的test.js,可以确认两个关键函数:

a0_0x411f3a(url)// 包装 URL,追加 FECU 参数a0_0x56ab93()// 生成 FECU 原始值

在可读化后的逻辑中,a0_0x411f3a对应类似ca(c),内部会调用_()生成 FECU,再通过da(c, e)拼接到 URL:

functionca(c){varf=_();returnda(c,f);}functionda(c,e){varb=c.split("?").length>1?"&FECU=":"?FECU=";returnc.replace(/^\s+|\s+$/g,"")+b+encodeURIComponent(e);}

这里要注意:最终 URL 中看到的FECU是经过encodeURIComponent编码后的值。

3. FECU 的真实生成结构

核心生成函数_()的逻辑大致如下:

function_(){O=u();// 当前时间戳a=$();// 读取服务 cookieaa();// 初始化 fingerprintT["0"]=c(a);// cookie md5T["8"]=sessionStorage.getItem("fi")||c(JSON.stringify("none"));T["9"]=c(navigator.userAgent);T["10"]=m();// 浏览器类型检测T["2"]=w();// webdriver 检测T["3"]=d;// debugger 检测T["4"]=I();// 调试检测T["11"]=O-P+Q*1000;T["12"]=p();// 5 位随机字符串returnZ(o(T));// 序列化并加密}

可以看到FECU不是单纯哈希,而是多个环境字段拼接后再加密,字段包括:

FECW / FECL / FECN cookie sessionStorage.fi navigator.userAgent 浏览器检测结果 webdriver 检测结果 debugger 检测结果 服务端时间差 随机字符串 页面配置派生出的 key

其中Z(o(T))是最终加密出口,o(T)负责序列化,Z()使用页面配置中的R.key派生密钥进行加密。

4. 关键问题:离线环境缺少#wsyzwdbq

最初离线运行时生成的是 173 位左右的错误值,例如:

dU+iHqmfHJ/cElrxC0tD3S...

而网页生成的值解码后是 194 位。通过 Node 直接运行fecu_runtime.js发现报错:

getConfig error

继续追踪r()函数后发现,JS 会从页面隐藏配置中解析出R.keyserver_timesecure等字段:

functionr(){vara=window.fecBaseConfig_wsyzwdbq||document.getElementById("wsyzwdbq").innerHTML.replace(/[\r\n]/g,"");varb=a.split(",");// 经过固定索引重排,派生 key、server_time、is_debugger、securereturn{key:r,server_time:j,is_debugger:k,secure:l};}

离线补环境中没有window.fecBaseConfig_wsyzwdbq,也没有document.getElementById("wsyzwdbq"),导致R.key为空,生成结果自然走错分支。

5. 使用 MCP 从真实浏览器采集配置

通过启动带远程调试端口的 Chrome:

chrome.exe--remote-debugging-port=9222 `--user-data-dir=D:\Desktop\codex_api\GH\chrome-debug-profile` https://m.airchina.com.cn/c/invoke/booking/showFlights@pg

然后用js-reverse-mcp在页面上下文中执行:

(()=>{return{wrapped:a0_0x411f3a('/g/invoke.json222'),cookie:document.cookie,fecBaseConfig:window.fecBaseConfig_wsyzwdbq||document.getElementById('wsyzwdbq')?.innerHTML.replace(/[\r\n]/g,''),fi:sessionStorage.getItem('fi'),ua:navigator.userAgent};})()

得到真实页面中的关键配置:

#wsyzwdbq: 3dc6cadce1502814c1a8516993f11e2c, 1ec374c607689d420fb3dc7d87169dbf, ... 6c39eb66402fff30b91bb61f08db4c64 cookie: FECW=54b6ab449c43b66b9c9a9ff0c8762dd...

这一步确认了:离线生成缺的不是算法,而是页面运行时配置和 cookie 环境。

6. 补环境实现离线生成

fecu_runtime.js中补上必要浏览器环境:

varDEFAULT_FEC_BASE_CONFIG='3dc6cadce1502814c1a8516993f11e2c,...';global.fecBaseConfig_wsyzwdbq=DEFAULT_FEC_BASE_CONFIG;document.getElementById=function(id){if(String(id)==='wsyzwdbq'){return{innerHTML:global.fecBaseConfig_wsyzwdbq||DEFAULT_FEC_BASE_CONFIG};}returnnull;};document.forms=[];window.chrome={runtime:{}};global.chrome=window.chrome;

同时增加统一参数入口:

functionapplyOptions(options){options=options||{};if(options.fecBaseConfig){global.fecBaseConfig_wsyzwdbq=String(options.fecBaseConfig).replace(/[\r\n]/g,'');window.fecBaseConfig_wsyzwdbq=global.fecBaseConfig_wsyzwdbq;}if(options.cookies)setCookies(options.cookies);if(options.localStorage){Object.keys(options.localStorage).forEach(function(key){localStorage.setItem(key,options.localStorage[key]);});}if(options.sessionStorage){Object.keys(options.sessionStorage).forEach(function(key){sessionStorage.setItem(key,options.sessionStorage[key]);});}}

这样execjs调用时就可以把真实网页参数传进去。

7. Python 侧通过 execjs 调用

catch.py中保留 JS 编译缓存,并通过 payload 传入 cookie、页面配置和 storage:

@lru_cache(maxsize=1)def_load_fecu_ctx():returnexecjs.compile(FECU_RUNTIME_PATH.read_text(encoding='utf-8'))defgenerate_fecu_offline(url:str='/g/invoke.json')->str:payload={'cookies':cookies,'fecBaseConfig':os.getenv('AIRCHINA_FEC_BASE_CONFIG',DEFAULT_FEC_BASE_CONFIG),'localStorage':{'H5_KEY':os.getenv('AIRCHINA_H5_KEY','2CB6E10B73D101E7'),'tnum':os.getenv('AIRCHINA_TNUM','"hk6pLR0O2iwLwY0Yl/9Tzw=="'),'fVFlag':os.getenv('AIRCHINA_FVFLAG','"W6muJhRLQKHltvIqWvW2HA=="'),},'sessionStorage':{'fi':os.getenv('AIRCHINA_FI','c56a66b07a8623afbfe503ef428f5fe3'),},}return_load_fecu_ctx().call('get_fecu',url,payload)

验证结果:

python-mpy_compile catch.py python catch.py --fecu-only

此时本地可以稳定生成 194 位解码态 FECU,说明已经进入正确生成分支。

8. 为什么网页上看到 202 位以上?

提出疑问:网页实时测试很多组,最少都是 202 位,为什么离线只有 194 位?

原因是统计口径不同。网页 URL 中的FECU是编码态:

encodeURIComponent(fecu)

原始 FECU 中如果出现/+,会被编码成 3 个字符:

/ -> %2F + -> %2B

所以长度关系是:

编码后长度 = 原始长度 194 + 2 * (原始 FECU 中 / 和 + 的数量)

例如用户样本:

encoded length: 204 decoded length: 194 + 数量: 3 / 数量: 2

计算:

194 + 2 * (3 + 2) = 204

因此网页看到的 202、204、206、208 都是编码态长度波动;原始 FECU 解码后仍然是 194 位。

9. 固定前缀由什么决定?

继续分析发现,FECU 的固定前缀主要由FECW这个 cookie 决定,当前页面R.secure === "2",因此$()函数读取的是:

function$(){if(R.secure==="2"){returns("FECW")||"";}elseif(R.secure==="3"){returns("FECN")||"";}else{returns("FECL")||"";}}

而生成时第一项就是:

T["0"]=c(FECW);

实验结果:

原始 FECW -> 8BznAJJV3izo... 删除 FECW -> dZ3gV/tvDz61... FECW 改成 abc -> 1+eXFlA2QX8n... fi 改变 -> 前缀不变

所以前缀主因是:

FECW + 页面配置派生出的 R.key

随机串、时间戳、fi等更多影响中后段,不决定最前面的固定前缀。

10. 总结

本次分析的关键不是单纯“抠算法”,而是还原完整浏览器运行环境。最初离线生成错误,是因为缺少网页隐藏配置#wsyzwdbq,导致R.key为空;补齐页面配置、cookie、storage、UA、Chrome 环境后,execjs能够脱离浏览器生成正确结构的 FECU。最终确认:

a0_0x411f3a(url) 负责 URL 包装 a0_0x56ab93 / _() 负责生成 FECU #wsyzwdbq 决定 R.key、server_time、secure FECW 决定 FECU 前缀 FECU 原始长度为 194 URL 编码后通常显示为 202+

至于python catch.py返回body: error,这不是 FECU 函数本身的异常,而是服务端还会校验 live session、cookie、checkToken、业务参数等上下文。FECU 只是接口校验链路中的一环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询