文章目录
- [1. 写在前面](#1. 写在前面)
- [2. 游客注册](#2. 游客注册)
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
在作者之前的文章中写过关于某果登录算法以及登录后的下单、查询流程及相关风控分析: Apple登录下单算法及流程逆向分析,除此之外还有另外一种方式(游客下单),无需账号登录直接注册激活一次性游客cookies然后用它去add bag商品绑定就可以直接下单
大致的流程如下所示:
商品链接页访问-> 游客CK是初始化 -> Beacon/DC -> Shield风控验证 -> 激活Cookie
加购物车 -> Echo埋点上报 -> 游客上报登录 -> 配送选择 -> 收货配置 -> 支付 -> 完成
2. 游客注册
apple的游客生成依赖于触发多个接口发包然后拿到服务端返回的Set-Cookie,核心细节就是CK中的部分字段初始化由算法生成,然后个别字段由算法计算提交接口下发,如下是整个游客CK中比较关键的一些字段信息,如下所示:
s_vi: 访客ID、JS外链可以触发也可以算法生成、保持链路一致
s_fid: 客户端ID、初始算法生成、保持链路一致
as_atb: add bag 后给出的token
shld_bt_ck: 两个shiled接口触发,解number数组提交授权
shld_bt_m: 风控链路参数、每一级的接口都需继承更新
as_ltn_us: 注册激活成功给的登录状态标识
在init接口处请求的时候,需要携带一个fnode参数,这个参数它不是算法生成的,在商品首页访问的HTML中返回(唯一且动态),除此参数外还有一个as_sfa的CK参数,在请求atb接口的时候如上一样前置商品首次访问的时候提取出来即可,如下所示:


结束init跟atb接口后就能拿到服务器端Set-Cookie信息,直接更新到Session会话中再去完成后续的接口触发,往下新增了一个s_fid字段参数,初始算法生成即可,如下所示:
python
def generate_s_fid() -> str:
return "-".join(
"".join(secrets.choice("0123456789ABCDEF") for _ in range(16))
for _ in range(2)
)
再往下就来到了两次shld接口请求,第一次请求返回JSON,这就类型shld下发过来的题目(需要计算参数)如下所示:

通过对JSON数据的计算,得到第二次shld接口请求中的number参数,计算方式通过调用栈可以看到指向verify.js文件,代码是混淆过的,如下所示:


number就是在这里面计算的,解混淆后发现这个verify文件主要就是风控验证(包含了检测自动化环境),关于number的解法即给定了low、 high、parts、result,来求长度为parts 的数组,使其乘积等于result,且每个元素在[low, high] 内!解混淆后的核心源码如下所示:
javascript
async function search(currentFactors, currentProduct, startFactor) {
// 1. 超时检查
if (isTimeout()) return null;
// 2. 已找到 parts 个因数,且乘积等于 result
if (BigInt(currentFactors.length) === targetLen) {
if (currentProduct === targetProd) return [...currentFactors];
return null;
}
// 3. 枚举 startFactor ~ maxIterations
for (; startFactor <= maxIterations; startFactor++) {
if (startFactor < minFactor) continue;
if (currentProduct * BigInt(startFactor) > targetProd) continue;
// 剪枝:若 result 不能被 (currentProduct * factor) 整除,且还没凑够 parts 个,则跳过
if (targetProd % (currentProduct * BigInt(startFactor)) !== zero && ...) continue;
currentFactors.push(startFactor);
const result = await search(currentFactors, currentProduct * BigInt(startFactor), startFactor);
if (result) return result;
currentFactors.pop();
}
return null;
}
/**
* 因数分解算法 - 找出满足条件的质因数组合
*/
async function findFactorDecomposition(minFactor, maxIterations, targetLength, targetProduct, startTime, timeout) {
function isTimeout() {
const elapsed = new Date() - startTime;
const limit = timeout || 15000;
return elapsed > limit;
}
const targetLen = BigInt(targetLength);
const targetProd = BigInt(targetProduct);
const zero = BigInt(0);
const one = BigInt(1);
async function search(currentFactors, currentProduct, startFactor) {
if (isTimeout()) return null;
if (BigInt(currentFactors.length) === targetLen) {
if (currentProduct === targetProd) return [...currentFactors];
return null;
}
for (; startFactor <= maxIterations; startFactor++) {
if (startFactor < minFactor) continue;
if (currentProduct * BigInt(startFactor) > targetProd) continue;
if (targetProd % (currentProduct * BigInt(startFactor)) !== zero && BigInt(currentFactors.length) + one < targetLen) continue;
currentFactors.push(startFactor);
const result = await search(currentFactors, currentProduct * BigInt(startFactor), startFactor);
if (result) return result;
currentFactors.pop();
}
return null;
}
return await search([], one, minFactor);
}
/**
* 执行分解计算
*/
async function runDecomposition(params) {
const startTime = new Date();
let factors = null;
factors = await findFactorDecomposition(
params?.low,
params?.high,
params?.parts,
params?.result,
startTime,
params?.timeout
);
const took = new Date() - startTime;
if (factors == null) factors = [];
return {
"number": factors,
"took": took
};
}
Pow计算通过因数分解增加自动化成本,需在限定时间内完成!wd、patStts、patSkip相关的环境指纹用于区分浏览器类型与状况,还有chCTo、reqTo关联的超时标记暴露计算/请求超时,服务端可据此降权或拦截,number的DFS算法实现如下:
python
def solve_number_array(low, high, parts, result, timeout_ms):
def dfs(current, prod, start_val):
if len(current) == parts:
return current if prod == result else None
for v in range(start_val, high + 1):
if v < low or prod * v > result:
continue
if result % (prod * v) != 0 and len(current) + 1 < parts:
continue
current.append(v)
res = dfs(current, prod * v, v)
if res is not None:
return res
current.pop()
return None
nums = dfs([], 1, low) or []
took_ms = int((time.time() - start) * 1000)
return nums, took_ms
最终提交参数到二次Shield接口进行验证,拿到激活可用的shld_ck参数,不然后续的流程基本都是541风控问题,参数如下所示:
python
{
"high": shld_data["high"],
"low": shld_data["low"],
"parts": shld_data["parts"],
"result": shld_data["result"],
"signature": shld_data["signature"],
"X": shld_data["X"],
"t": shld_data["t"],
"timeout": shld_data["timeout"],
"flagskv": {"patSkip": True, "chCTo": True},
"number": number_list,
"took": took_ms,
}
之后就是一系列的加车、attach流程了,还有一个Echo的埋点大JSON,可以尽量去模拟,如下所示:

固定也是可以的,但是在生产环境持续的对抗中是高危的行为,往往很多埋点风控就是通过某些字段进行实时或离线分析再判研的!最终所有的调用接口如下(从游客注册到再下单验卡支付):
python
1. GET product_link → 获取 fnode, as_sfa
2. GET /shop/beacon/atb
3. GET /shop/mdp/echo/echo.png
4. GET /shop/dc
5. GET /shop/favoritesx/fetch
6. GET /shop/sba/d/init?fnode=&product=
7. GET /shop/shld/work/v1/q?wd=0
8. POST /shop/shld/work/v1/q?wd=0 → 提交 number 数组
9. GET /shop/updateSummary (×2)
10. GET /shop/api/purchase-options
11. GET /shop/api/applecare/data
12. GET /shop/fulfillment-messages
13. GET /shop/updateSEO
14. GET product_link?add-to-cart=add-to-cart&atbtoken=...
15. GET /shop/buy-iphone/iphone-17-pro?step=attach
16. GET /shop/beacon/atb
17. GET /shop/buyFlowAttachSummary/{PRODUCT}
18. GET /shop/favoritesx/fetch
19. POST /shop/mdp/api/echo → Echo 埋点
20. GET /shop/bag → 获取 x-aos-stk
21. POST /shop/bagx?_a=calculate
22. POST /shop/bagx/checkout_now
23. GET {api_domain}/shop/signIn?ssi=&up=t
24. POST {api_domain}/shop/signInx → 游客登录
25. POST {api_domain}/shop/checkout/start
26. GET {api_domain}/shop/checkout
27. POST {api_domain}/shop/checkoutx/fulfillment (×2)
28. POST {api_domain}/shop/addressx/ziplookup
29. POST {api_domain}/shop/checkoutx/shipping (×2)
30. POST {api_domain}/shop/checkoutx/billing (×N 礼品卡)
31. POST {api_domain}/shop/checkoutx/billing → continueFromBillingToReview
整个流程下来基本就是需要一点时间去分析API的加载流程,另一个就是解决获取shld_ck的那个number数组要求正确求解(在规定的timeout内提交),还有就是x-aos-stk会话认证参数,这个是不可以伪造的,跟前置参数提取一样通过checkout接口提取,最终实现如下:

2025年的班就上到这个时候了,明年在精进自己!提前预祝各位新年快乐~