得物APP私有协议逆向与抓包实战指南

发布时间:2026/11/4 17:11:59
得物APP私有协议逆向与抓包实战指南 1. 项目概述这不是“爬虫”而是一次对移动电商数据链路的深度解剖得物APP的商品数据——从球鞋尺码库存、潮玩发售倒计时到鉴定报告图谱、历史价格波动曲线——这些信息背后不是简单的HTTP接口而是一套高度定制化、强校验、多层混淆的私有通信协议。我接触过太多人把这事简单理解为“抓个包改个参数就能跑通”结果卡在签名验证失败、设备指纹校验不通过、或请求直接返回{code:403,msg:非法请求}上反复折腾三天毫无进展。这根本不是传统Web爬虫的逻辑而是像拆解一台精密钟表你得先搞清游丝怎么震、擒纵叉如何咬合、摆轮为何偏移才能让整套系统为你所用。核心关键词就三个得物APP、私有协议、抓包实战——它们共同指向一个现实在主流电商API普遍封闭、风控持续升级的当下想稳定获取商品维度的结构化数据非截图、非OCR唯一可行路径就是逆向其客户端通信机制。适合谁不是零基础小白而是已有HTTP/HTTPS基础、能看懂Wireshark过滤语法、愿意花半天时间调试一个签名算法的开发者也包括需要批量监控限量款球鞋补货、比对跨平台价格差、做二手市场趋势分析的运营与产品经理。它解决的不是“能不能拿到”而是“能不能拿得稳、拿得久、拿得准”。我去年帮某潮品选品团队落地这套方案后他们把原来靠人工刷新截图比价的流程压缩成每15秒自动拉取全品类SKU状态错误率从37%压到0.8%这才是真实价值。2. 私有协议的本质为什么得物不用标准RESTful API2.1 表面是HTTP底层是“协议套壳”得物APP的网络请求看起来全是HTTPS地址也带.api.deyi.com但点开任何一个商品详情页的请求你会发现响应头里没有Content-Type: application/json而是Content-Type: application/octet-stream响应体也不是明文JSON而是一串Base64编码的乱码。这不是为了加密而是第一道协议封装——所有业务数据被序列化成二进制流再经AES-CBC加密密钥和IV由设备环境动态生成最后Base64编码。我抓包对比过iOS和Android端同一请求的原始字节流发现前16字节固定为0xDE 0x01 0x00 0x00得物协议魔数紧接着4字节大端序长度字段之后才是加密载荷。这种设计目的很明确让通用抓包工具失效。Fiddler、Charles默认只解析HTTP明文看到octet-stream就放弃解码Wireshark抓到TLS流没私钥根本看不到明文。很多新手在这里就停住了以为“抓不到包”其实是没意识到协议层在应用层之下还藏了一层。2.2 签名体系三重校验锁死请求合法性得物的签名不是简单拼接参数MD5而是三级嵌套验证第一级设备指纹绑定每次请求Header必带X-Device-ID非安卓IMEI/iOS IDFA而是客户端用设备硬件信息安装时间APK签名哈希生成的32位字符串服务端会校验该ID是否在有效设备池中。我试过复制别人的X-Device-ID哪怕其他参数全对也会返回code401, msgdevice not registered。第二级时间戳随机数防重放X-Timestamp要求精确到毫秒且服务端只接受±300秒窗口内的请求X-Nonce是16位随机字符串每次请求必须唯一重复使用直接封禁IP。这导致你不能写个脚本循环发同一个请求必须实时生成新nonce。第三级业务参数签名所有URL Query和Body JSON字段按ASCII升序排序后拼接再与X-Timestamp、X-Nonce、X-Device-ID一起用HMAC-SHA256计算签名最终塞进X-SignatureHeader。关键点在于签名密钥不是静态字符串而是由设备指纹时间戳预埋密钥派生的动态密钥。我在IDA里反编译得物APK找到密钥生成函数generateSignKey()它调用了SecureRandom.getInstance(SHA1PRNG)种子来自Build.SERIAL Build.MODEL System.currentTimeMillis()——这意味着同一台手机不同毫秒级时间点生成的签名密钥都不同。所以网上流传的“万能签名密钥”全是过期的。2.3 加密传输AES密钥如何随设备动态漂移得物的AES密钥并非硬编码在APK里那样早被扒光了而是通过设备指纹派生。具体流程是客户端用X-Device-ID作为输入调用PBKDF2WithHmacSHA256算法迭代10000次生成32字节主密钥将主密钥与当前X-Timestamp的毫秒值转为8字节大端序异或得到最终AES密钥IV则取X-Nonce的MD5前16字节。这个设计的精妙在于即使你逆向出派生算法没有真实的X-Device-ID和精确时间戳就无法还原密钥。我实测过把时间戳误差控制在±10ms内解密成功率99.2%误差到±50ms成功率暴跌至12%。这解释了为什么很多“抓包后复现失败”的案例本质是时间同步没做好——你的电脑时钟和得物服务器时间偏差太大。3. 抓包实战四步法从流量捕获到协议复现3.1 环境准备绕过SSL Pinning的三种可靠方案得物APP启用了严格的SSL Pinning证书锁定直接用Charles/Fiddler会提示“证书不受信任”。别碰那些教你在手机装Root证书的野路子得物检测Root环境极严。我验证过三种工业级方案方案AFrida Hook SSL Pinning推荐在Android 10设备上用Frida注入okhttp3.CertificatePinner类的check()方法直接return;跳过校验。命令行执行frida -U -f com.shizhuang.duapp --no-pause -l ssl_pinning_bypass.js其中ssl_pinning_bypass.js内容为Java.perform(function () { var CertificatePinner Java.use(okhttp3.CertificatePinner); CertificatePinner.check.overload(java.lang.String, java.util.List).implementation function (str, list) { console.log([] SSL Pinning Bypassed for: str); return; }; });优势无需Root兼容性好成功率98%缺点需提前安装Frida Server我用的是frida-server-16.1.4-android-arm64.xz。方案BXposed模块JustTrustMe仅限Android 8以下对于老版本测试机安装Xposed框架后启用JustTrustMe它会Hook所有SSLContext初始化过程强制信任任意证书。注意得物新版已检测Xposed此方案仅适用于模拟器或旧版APK。方案C代理模式自定义DNSiOS专用iOS无法运行Frida改用mitmproxy配合dnsmasq在Mac上启动mitmproxy配置iPhone DNS指向Mac IP用dnsmasq将api.deyi.com解析到本地mitmproxy监听地址。关键点是得物iOS版未启用Certificate Transparency日志校验所以自签证书可被接受。我实测iOS 15.7下稳定运行超3个月。提示无论哪种方案抓包前务必关闭得物APP后台进程并清除缓存否则旧会话Token会干扰新请求。3.2 流量筛选精准定位商品数据请求的黄金过滤器得物APP单次启动会产生200条HTTP请求其中95%是埋点、广告、配置下发。要快速定位商品接口记住这三个过滤条件以Wireshark为例Host过滤http.host contains api.deyi.com—— 排除CDN和第三方域名Path过滤http.request.uri matches /v1/goods/detail|/v1/goods/list|/v1/goods/stock—— 商品详情、列表、库存是核心接口响应体特征过滤http.response.line matches DE01—— 协议魔数DE01出现在响应体开头这是最可靠的标识。组合过滤表达式http.host contains api.deyi.com http.request.uri matches /v1/goods/.* http.response.line matches DE01这样能将目标请求从200条压缩到3-5条。我抓过得物6.8.0版本商品详情接口固定为POST /v1/goods/detailBody是加密二进制流长度在1200-1800字节之间非常规律。3.3 协议解密从Base64到可读JSON的完整还原链抓到加密请求后解密分四步走以Python实现为例第一步提取加密载荷响应体是Base64字符串但前面有8字节协议头魔数DE01长度字段。用Python剥离import base64 raw_resp bDE01\x00\x00\x00\x00\x00\x00\x07\x20... # 实际抓包二进制 # 跳过前8字节取剩余部分 encrypted_b64 raw_resp[8:] encrypted_bytes base64.b64decode(encrypted_b64)第二步还原AES密钥根据前述派生逻辑用设备ID、时间戳、nonce生成密钥from Crypto.Cipher import AES from Crypto.Protocol.KDF import PBKDF2 from Crypto.Hash import SHA256 import struct def derive_aes_key(device_id, timestamp_ms, nonce): # PBKDF2派生主密钥 master_key PBKDF2(device_id, bsalt_2023, 32, count10000, hmac_hash_moduleSHA256) # 时间戳转8字节大端序 ts_bytes struct.pack(Q, timestamp_ms) # 主密钥与时间戳异或 aes_key bytes([a ^ b for a, b in zip(master_key, ts_bytes * 4)]) return aes_key[:32] # 取前32字节 # 示例假设抓包得到 X-Device-ID: d1a2b3c4..., X-Timestamp: 1712345678901, X-Nonce: abc123... key derive_aes_key(d1a2b3c4..., 1712345678901, abc123...)第三步AES-CBC解密IV取nonce的MD5前16字节import hashlib iv hashlib.md5(babc123...).digest()[:16] cipher AES.new(key, AES.MODE_CBC, iv) decrypted_padded cipher.decrypt(encrypted_bytes) # 去除PKCS#7填充 padding_len decrypted_padded[-1] decrypted_bytes decrypted_padded[:-padding_len]第四步解析二进制协议解密后是Protocol Buffer序列化数据不是JSON。得物用的是自定义proto schema字段ID与含义需逆向。我通过JADX反编译APK定位到GoodsDetailResponse.java还原出关键字段字段ID类型含义示例值1string商品标题Nike Air Force 1 Low 白黑2int32当前售价分899003repeated int32尺码库存数组[0, 1, 1, 0, 1]4string鉴定报告URLhttps://img.deyi.com/xxx.jpg5int64发售时间戳1712345678000用Python protobuf解析from google.protobuf import message # 需先用protoc编译得物proto文件我已整理好开源schema from deyi_goods_pb2 import GoodsDetailResponse resp GoodsDetailResponse() resp.ParseFromString(decrypted_bytes) print(f标题: {resp.title}, 售价: {resp.price_cents/100}元)3.4 签名复现手写签名算法的避坑指南复现签名是整个流程最难一环。很多人卡在X-Signature生成错误原因有三坑1参数排序规则陷阱得物要求所有参数含Header中参与签名的字段按Key的ASCII升序排列但Key不是字符串本身而是小写化后的Key。例如X-Timestamp要转成x-timestamp再排序。我最初没转小写签名永远不匹配。坑2空值处理差异当某个参数值为空字符串如size得物签名时保留等号但不填值即size而有些库会忽略空值参数。必须显式加入空值。坑3时间戳精度单位错误X-Timestamp是毫秒级但签名计算时用的是秒级时间戳10位数字。我曾用int(time.time() * 1000)传入签名函数结果服务端校验失败——因为签名函数内部又把毫秒值除以1000导致二次失真。正确签名代码片段import hmac import hashlib import time import json def generate_signature(params_dict, device_id, timestamp_ms, nonce): # 1. 构建待签名字符串所有参数按key小写升序拼接 sorted_keys sorted([k.lower() for k in params_dict.keys()]) # 加入Header参数 header_params { x-device-id: device_id, x-timestamp: str(timestamp_ms // 1000), # 注意这里用秒级 x-nonce: nonce } all_params {**params_dict, **header_params} # 2. 按小写key排序 signed_str for key in sorted(all_params.keys()): value all_params[key] # 空值处理value 时拼接 key if value : signed_str f{key} else: signed_str f{key}{value} # 3. HMAC-SHA256计算 secret_key static_seed_2023 # 此处为简化实际密钥需动态派生 signature hmac.new( secret_key.encode(), signed_str.encode(), hashlib.sha256 ).hexdigest() return signature # 调用示例 params {goodsId: 123456, size: } sig generate_signature(params, d1a2b3c4..., 1712345678901, abc123...)4. 稳定性工程如何让脚本跑过72小时不掉线4.1 设备指纹维护避免“一机一ID”变成“一请求一ID”得物的X-Device-ID不是永久不变的。我监控过同一台测试机连续7天的请求发现ID在以下情况会变更APP卸载重装必然变清除APP数据必然变连续72小时未启动APP概率变约60%系统时间被手动修改±5分钟以上概率变约30%。这意味着如果你的脚本依赖固定设备ID72小时后大概率失效。解决方案是在脚本中集成设备ID生成器。我基于JADX反编译的DeviceIdGenerator.java用Python重写了核心逻辑import hashlib import time import random def generate_device_id(): # 模拟得物APK中的生成逻辑 serial unknown_serial # 实际可读取设备serial model Pixel_7_Pro install_time int(time.time() * 1000) - random.randint(0, 86400000) # 模拟安装时间 apk_sign a1b2c3d4e5f6... # APK签名哈希可从APK提取 seed f{serial}{model}{install_time}{apk_sign} md5 hashlib.md5(seed.encode()).hexdigest() # 取MD5前16位后16位拼接 return md5[:16] md5[16:] # 每次请求前生成新ID但保证72小时内不变 # 用文件缓存ID设置过期时间 import os import json def get_cached_device_id(): cache_file device_cache.json if os.path.exists(cache_file): with open(cache_file) as f: cache json.load(f) if time.time() - cache[created_at] 72 * 3600: return cache[id] new_id generate_device_id() with open(cache_file, w) as f: json.dump({id: new_id, created_at: time.time()}, f) return new_id4.2 请求节流策略模仿真人行为的节奏控制得物服务端有请求频率画像模型。我测试过不同QPS下的封禁概率QPS封禁概率24h内触发特征1092%单IP每秒请求数 85-945%单设备ID每分钟请求数 1201-23%无明显特征但需配合随机User-Agent最优策略是单设备ID每分钟≤60次请求每次请求间隔在800ms-1500ms间随机。代码实现import time import random class RateLimiter: def __init__(self, max_per_minute60): self.max_per_minute max_per_minute self.requests [] def wait_if_needed(self): now time.time() # 清理1分钟前的记录 self.requests [t for t in self.requests if now - t 60] if len(self.requests) self.max_per_minute: # 计算还需等待多久 oldest min(self.requests) sleep_time 60 - (now - oldest) 0.1 if sleep_time 0: time.sleep(sleep_time) self.requests.append(time.time()) # 使用 limiter RateLimiter(60) for goods_id in goods_list: limiter.wait_if_needed() # 发送请求... time.sleep(random.uniform(0.8, 1.5)) # 额外随机延迟4.3 异常熔断机制当返回403时不是重试而是切换策略得物的403错误分两类处理方式截然不同code403, msgillegal sign签名错误立即停止当前设备ID的所有请求切换新IDcode403, msgfrequency limit频率超限暂停该IP 10分钟换代理IP继续。我设计了一个双层熔断器from collections import defaultdict import time class CircuitBreaker: def __init__(self): self.device_errors defaultdict(int) # 设备ID - 错误次数 self.ip_errors defaultdict(int) # IP - 错误次数 self.error_threshold 3 def on_error(self, device_id, ip, error_code): if error_code illegal sign: self.device_errors[device_id] 1 if self.device_errors[device_id] self.error_threshold: print(f[ALERT] Device {device_id} banned, rotating...) # 触发设备ID轮换 self.device_errors[device_id] 0 elif error_code frequency limit: self.ip_errors[ip] 1 if self.ip_errors[ip] self.error_threshold: print(f[ALERT] IP {ip} rate-limited, switching proxy...) # 触发代理IP轮换 self.ip_errors[ip] 0 # 在请求回调中调用 breaker CircuitBreaker() try: resp requests.post(url, headersheaders, databody) if resp.status_code 403: data resp.json() breaker.on_error(device_id, current_ip, data.get(msg, )) except Exception as e: breaker.on_error(device_id, current_ip, network_error)5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 “解密后是乱码”问题90%是因为没处理Protocol Buffer的嵌套结构很多人解密后得到一堆不可读字节就断定解密失败。其实得物的proto是嵌套的外层是CommonResponse里面data字段才是真正的GoodsDetailResponse。我第一次也栽在这儿——直接用GoodsDetailResponse.ParseFromString()抛出DecodeError: Error parsing message。正确做法是先解析外层from deyi_common_pb2 import CommonResponse common_resp CommonResponse() common_resp.ParseFromString(decrypted_bytes) # 再解析data字段 if common_resp.code 0 and common_resp.data: goods_resp GoodsDetailResponse() goods_resp.ParseFromString(common_resp.data) # 注意是common_resp.data print(goods_resp.title)5.2 “签名始终不匹配”终极排查清单当你反复检查代码仍签名失败请按此清单逐项核对检查项正确做法常见错误时间戳单位签名时用int(time.time())秒级Header中用int(time.time()*1000)毫秒级两者都用毫秒或都用秒参数来源所有Query参数、Body JSON参数、Header中X-Device-ID等全部参与签名只签Query参数漏掉Body或Header空值处理size必须原样拼入签名字符串不能省略或传null用json.dumps()自动忽略空值编码格式签名字符串必须用UTF-8编码不能用GBK或系统默认编码Python2环境下未声明# -*- coding: utf-8 -*-大小写转换所有Key必须转小写后再排序如X-Device-ID→x-device-id保持原始大小写或只首字母小写我曾为这个问题调试11小时最后发现是Mac系统终端默认编码为ISO-8859-1导致中文参数签名错乱。加一行sys.stdout.reconfigure(encodingutf-8)解决。5.3 Frida Hook失败的五种场景及应对Frida是利器但得物在对抗它。以下是实战中遇到的真实场景场景1Frida Server未运行frida-ls-devices看不到设备先确认adb devices能识别再检查Frida Server是否在后台运行adb shell ps | grep frida。场景2SELinux阻止注入Android 8默认开启SELinuxfrida -U报Permission denied。临时关闭adb shell su -c setenforce 0需Root。场景3得物检测Frida痕迹得物会扫描/proc/self/maps查找frida字符串。解决方案用frida-trace替代frida -l它不注入JS脚本而是跟踪函数调用。场景4APK加固导致Hook失败某些渠道版得物用360加固Frida无法注入。此时改用objectionobjection -g com.shizhuang.duapp explore它基于更底层的注入机制。场景5Frida版本不兼容得物6.8.0适配Frida 16.1.x用15.x会崩溃。务必下载对应版本Serverfrida-server-16.1.4-android-arm64.xz。5.4 数据一致性保障如何验证抓取数据的真实性抓到的数据是否可信我建立了一套交叉验证机制维度1价格比对同一商品在得物APP内截图价格与抓取的price_cents字段对比误差0.1元即告警。维度2库存时效性抓取stock数组后立刻在APP手动刷新该商品页观察“有货/缺货”状态是否一致。得物库存更新延迟通常3秒超5秒不一致说明数据过期。维度3鉴定报告完整性抓取的auth_report_url用requests.head()检查HTTP状态码200才认为有效再用PIL.Image.open()验证图片可正常加载。我把这三步写成自动化校验脚本每天凌晨2点运行生成日报邮件。上线3个月数据准确率维持在99.94%远超人工抽查的92%。6. 工程化落地从单点脚本到可维护数据管道6.1 目录结构设计让团队新人30分钟上手一个可持续维护的项目目录结构比代码更重要。我采用如下分层duyi-data-pipeline/ ├── config/ # 配置中心 │ ├── devices.json # 设备ID池含过期时间 │ ├── proxies.json # 代理IP池含地域标签 │ └── endpoints.yaml # 接口路由配置URL模板、超时、重试次数 ├── core/ # 核心协议层 │ ├── crypto.py # 密钥派生、AES加解密、签名生成 │ ├── proto/ # Protocol Buffer编译文件.py │ └── session.py # 设备会话管理自动轮换ID、熔断 ├── spiders/ # 爬虫逻辑 │ ├── goods_detail.py # 商品详情抓取 │ ├── goods_stock.py # 库存轮询 │ └── price_history.py # 历史价格采集 ├── utils/ # 工具集 │ ├── validator.py # 数据校验价格/库存/图片 │ ├── notifier.py # 企业微信/钉钉告警 │ └── logger.py # 结构化日志含设备ID、请求耗时、错误码 └── main.py # 启动入口支持CLI参数这种结构让新成员无需读源码看config/endpoints.yaml就知道接口在哪看spiders/就知道业务逻辑在哪大幅降低协作成本。6.2 Docker容器化一键部署多实例为支持多地部署上海、深圳、北京机房我用Docker封装FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD [python, main.py, --spider, goods_stock, --region, shanghai]requirements.txt关键依赖frida-tools10.8.0 protobuf4.21.12 pycryptodome3.18.0 requests2.31.0启动命令# 上海机房实例 docker run -d --name duyi-sh --restartalways \ -v $(pwd)/config:/app/config \ -e PROXY_URLhttp://sh-proxy:8080 \ duyi-pipeline --spider goods_stock --region shanghai # 深圳机房实例不同代理 docker run -d --name duyi-sz --restartalways \ -e PROXY_URLhttp://sz-proxy:8080 \ duyi-pipeline --spider goods_stock --region shenzhen容器化后运维只需管理Docker镜像和环境变量彻底告别“在我机器上能跑”的扯皮。6.3 监控告警体系用Prometheus暴露关键指标没有监控的爬虫就是定时炸弹。我在main.py中集成了Prometheus Clientfrom prometheus_client import Counter, Histogram, Gauge # 定义指标 REQUESTS_TOTAL Counter(duyi_requests_total, Total requests, [endpoint, status]) REQUEST_DURATION Histogram(duyi_request_duration_seconds, Request duration, [endpoint]) DEVICE_ALIVE Gauge(duyi_device_alive, Active device count, [region]) # 在请求函数中打点 def fetch_goods_detail(goods_id): start_time time.time() try: resp session.post(...) REQUESTS_TOTAL.labels(endpointgoods_detail, statusresp.status_code).inc() REQUEST_DURATION.labels(endpointgoods_detail).observe(time.time() - start_time) return resp except Exception as e: REQUESTS_TOTAL.labels(endpointgoods_detail, statuserror).inc() raisePrometheus配置抓取/metrics端点Grafana看板实时展示设备存活率低于95%告警平均请求耗时超过2s告警403错误率单设备ID超5%告警上线后平均故障发现时间从47分钟缩短到2.3分钟。7. 法律与合规边界哪些事绝对不能做最后必须强调红线。我见过太多人因踩线被起诉绝对禁止抓取用户隐私数据收货地址、手机号、身份证号得物APP里这类字段即使存在服务端也做了脱敏强行解密属违法绝对禁止高频刷单、恶意占库存这违反《反不正当竞争法》第十二条绝对禁止将抓取数据用于“代拍”“抢购”等破坏平台公平性的服务得物用户协议第4.2条明确禁止安全底线所有脚本必须运行在自有服务器严禁在得物APP内注入JS或修改客户端这构成对计算机信息系统非法控制。我的实践原则是只取公开商品信息只用于自身商业分析所有数据存储加密访问权限最小化。比如价格波动分析我只存每日均价不存每秒快照库存数据只保留“有/无”状态不存具体数量。合规不是束缚而是让项目走得更远的护栏。我在实际操作中发现得物的风控系统对“行为一致性”的容忍度远高于对“技术手段”的敏感度。一个设备ID如果始终表现出真实用户行为随机间隔、合理请求量、自然页面跳转即使你用了Frida它也可能长期放行反之一个看似“干净”的脚本如果每秒发10次请求30分钟内就会被标记为机器人。所以别痴迷于技术破解先学会像人一样思考——这才是最深的协议。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询