
1. 项目概述一次从“黑盒”到“白盒”的深度探索最近在技术圈里和几位做安全研究的朋友聊起航空领域的系统大家普遍觉得这是一个既神秘又充满挑战的领域。这些系统往往承载着海量的用户数据和复杂的业务逻辑其背后的技术架构和数据处理流程对于外部开发者而言常常像一个“黑盒”。恰好我手头有一个关于“东方航空一条龙服务”的逆向分析项目这并非指代任何具体的官方服务而是我们内部对一个模拟的、集成了航班查询、动态定价、用户画像等核心功能的综合性数据服务接口的代号。这个项目的目标就是通过纯技术手段在不接触任何内部源码和文档的前提下从网络交互层面入手逆向推演出这套服务的数据流转逻辑、核心算法模型以及最终的数据产出格式。这听起来有点像侦探破案只不过我们的“现场”是网络数据包“线索”是加密的API请求和响应。为什么要做这件事对于安全研究人员来说这是理解大型复杂系统潜在攻击面、验证其数据安全性的必经之路对于开发者而言这种逆向工程思维能极大地锻炼我们分析问题、解构系统的能力尤其是在面对第三方封闭系统时如何通过有限的外部信息洞察其内部运作机制。整个过程我们将严格遵循安全研究的伦理边界所有分析均基于公开可访问的接口和模拟数据绝不涉及对真实生产系统的未授权探测、干扰或数据窃取。本次分享我将完整复盘从抓包分析、协议解析、算法推测到数据还原的全流程并附上大量实操中踩过的坑和总结出的技巧。2. 逆向工程的核心思路与前期准备逆向一个完整的服务链条不能盲目地一头扎进数据包海洋。我们需要一个清晰的战略地图。整个“一条龙服务”可以抽象为客户端如App或网页发起请求 - 经过若干网关或中间件 - 抵达后端业务集群 - 处理并返回结果。我们的逆向工作就是沿着这条链路的反方向从最终呈现的数据结果和网络上的交互痕迹过程去反推其处理逻辑算法和初始状态输入。2.1 目标界定与工具选型首先必须明确我们的目标是“理解”而非“攻击”。因此所有分析活动都应局限在协议分析、逻辑推演和算法模拟的范畴。我们假设自己是一个“善意”的外部观察者试图构建一个与目标服务行为一致的模拟客户端或分析模型。工欲善其事必先利其器。以下是本次项目核心的工具栈每一款的选择都有其深意抓包与调试工具Charles / Fiddler / WiresharkCharles (首选)对于HTTP/HTTPS流量特别是移动端AppCharles的代理抓包和SSL证书安装流程最为成熟友好。它的“Map Local”和“Rewrite”功能在模拟响应、修改请求进行测试时无可替代。Fiddler与Charles类似在Windows平台下与.NET系应用集成度更高脚本扩展能力强。Wireshark当遇到非HTTP协议如某些自定义TCP/UDP协议或需要更底层网络分析时它是终极武器。但上手门槛稍高。选择理由Charles提供了从抓包、查看、修改到模拟的一站式可视化环境极大提升了逆向初期探索的效率。反编译与代码分析工具Jadx / Ghidra / IDAJadx针对Android App的APK文件能将其反编译为可读性极高的Java代码。这是洞察客户端逻辑、寻找API端点、加密密钥和算法线索的入口。Ghidra/IDA如果服务涉及本地原生库.so/.dll例如某些核心加密算法以C实现并打包在App内则需要这类反汇编工具进行静态分析。选择理由从客户端入手往往能找到服务器通信的“约定”比如签名算法、固定参数等这些是解密服务器响应的钥匙。编程与自动化脚本Python 相关库Requests模拟HTTP请求的基石。PyCryptodome / cryptography处理各种加密解密AES, RSA, DES、哈希MD5, SHA、编码Base64操作。BeautifulSoup4 / lxml解析HTML响应如果部分数据走Web端。Pandas / JSON对还原出的结构化数据进行整理和分析。选择理由Python生态丰富适合快速原型验证。当我们推测出某个加密算法或签名规则时可以立刻写脚本验证。辅助与协作工具Postman / Insomnia用于将Charles抓到的请求导出并在此类工具中构建参数化请求集合方便批量测试和团队共享。浏览器开发者工具 (F12)对于Web端服务其Network面板、Debugger和Console是分析JavaScript逻辑、追踪XHR/Fetch请求的利器。笔记工具 (如Obsidian, Notion)逆向过程会产生大量碎片信息URL、参数、响应片段、猜想一个能建立双向链接的笔记系统至关重要帮助梳理逻辑脉络。注意法律与道德红线在任何逆向工程开始前必须反复确认目标。仅针对自己拥有合法使用权的应用如自己购买的软件、自己公司开发的应用进行安全审计或明确允许安全研究的公开漏洞测试平台。绝对禁止对未授权的商业系统、政府系统或个人数据进行逆向分析这不仅是职业道德问题更可能触犯法律。2.2 环境搭建与初步侦察搭建一个干净的、可控的分析环境是第一步。我通常会使用一台专用的虚拟机或物理机安装好上述所有工具。然后在目标设备手机或电脑上配置代理指向运行Charles的机器。第一步捕获初始流量。启动Charles在目标设备上打开“东方航空”App或访问其官网进行最基础的操作比如一次简单的航班查询上海到北京明天。此时Charles的会话列表Session List会瞬间涌入大量请求。我们的首要任务是“去噪”。技巧一使用Filter过滤器。在Charles的Filter栏输入与目标域名相关的关键词如“ceair.com”、“easternair”等快速过滤出核心API请求。通常静态资源图片、CSS、JS和第三方追踪请求可以先忽略。技巧二关注高频和“胖”请求。那些在关键操作后重复出现、或者响应体Response特别大的请求往往是核心业务接口。例如一个名为/api/flight/search的POST请求其响应内容可能就包含了航班列表、价格等关键数据。初步侦察成果我们可能会发现一系列有规律的API端点例如/api/v1/user/login- 登录/api/v1/flight/search- 航班搜索/api/v1/flight/price- 价格详情/动态定价/api/v1/order/create- 创建订单/api/v1/payment/submit- 支付提交同时我们会立刻注意到两个关键点1. 几乎所有重要请求都是HTTPS的。2. 请求头和请求体中常带有一些看似随机的长字符串参数例如signature、nonce、timestamp响应体也常常是加密的或经过编码的密文。这标志着逆向工作进入了真正的核心阶段——协议与算法分析。3. 协议解析与算法逆向实战抓到了流量只是拿到了“加密的电报”。接下来要做的就是破译密码本。这一阶段是逆向工程中最具技术挑战性的部分。3.1 请求签名算法破解在抓到的/api/v1/flight/search请求中我们看到了如下关键参数POST /api/v1/flight/search HTTP/1.1 Host: api.ceair.com Content-Type: application/json X-App-Version: 6.12.0 X-Timestamp: 1646389200000 X-Nonce: a7f3d8e1 X-Signature: 4f89a1c3e0b2d5f876a... (很长一串16进制字符串) {depCity:SHA,arrCity:PEK,depDate:2023-10-01,flightType:OW}显然X-Signature是服务器用来验证请求合法性、防止篡改的签名。我们的目标就是找出这个签名的生成规则。方法一静态分析客户端代码。将App的APK文件拖入Jadx全局搜索“signature”、“sign”、“X-Signature”等关键词。通常会在网络请求库的拦截器Interceptor或工具类中找到相关代码。运气好的话你会直接看到类似下面的Java代码public static String generateSignature(MapString, String params, String secretKey) { // 1. 参数按Key排序 ListString keys new ArrayList(params.keySet()); Collections.sort(keys); // 2. 拼接成 key1value1key2value2... 的格式 StringBuilder sb new StringBuilder(); for (String key : keys) { sb.append(key).append().append(params.get(key)).append(); } // 3. 拼接密钥 sb.append(key).append(secretKey); // 4. 进行MD5哈希或SHA256等 return md5(sb.toString()).toUpperCase(); }如果找到了secretKey那就事半功倍。但更多时候secretKey可能是硬编码的经过混淆也可能是从服务器动态获取的。方法二动态调试与Hook。如果静态分析找不到或代码混淆严重就需要动用动态手段。对于Android可以使用Frida或Xposed框架Hook住签名生成函数直接打印出输入和输出。例如用Frida写一个脚本Java.perform(function() { var SignUtils Java.use(com.ceair.network.SignUtils); SignUtils.generateSignature.implementation function(params, key) { console.log(generateSignature called!); console.log(params: JSON.stringify(params)); console.log(key: key); var result this.generateSignature(params, key); console.log(result: result); return result; }; });运行脚本后在App里操作就能在控制台实时看到签名的生成过程。方法三黑盒测试与归纳。如果前两种方法都行不通就只能通过大量黑盒测试来归纳规律。用Postman构造多个请求系统性地改变参数如depCity、timestamp观察signature的变化。利用Python脚本批量测试寻找碰撞。例如固定其他参数只改变timestamp发现signature完全变化说明timestamp参与了签名。通过对比不同请求的签名结合常见的签名算法如HMAC-SHA256可以尝试推测。实操心得签名算法往往是“参数排序拼接密钥哈希”的组合。nonce随机数通常用于防止重放攻击它可能参与签名也可能单独放在 header 中。timestamp也会参与并且服务器会校验其时效性如允许5分钟误差。逆向签名最有效的方法是静态找到关键函数 动态Hook验证。3.2 响应数据解密解决了请求签名相当于拿到了对话的“入场券”。但服务器返回的数据很可能还是加密的。常见的响应体可能是一个Base64字符串或者直接是一段二进制数据。第一步判断加密类型。查看Response的HeaderContent-Type有时会给出线索。但更常见的是响应体是一个JSON但里面的关键数据如data字段是密文。例如{ code: 0, message: success, data: U2FsdGVkX12w7bT...很长... }这个data字段看起来像Base64编码。解码后可能是一段非文本的二进制数据这提示可能是对称加密如AES。第二步寻找密钥和IV。对称加密需要密钥Key和初始化向量IV。它们可能硬编码在客户端同样通过Jadx搜索“AES”、“DES”、“Cipher”、“密钥”等关键词。在登录后的某个接口返回有时密钥会随着会话令牌Token一起下发。由客户端动态生成并通过非对称加密如RSA安全地传递给服务器后续通信使用该对称密钥。这种情况下需要先逆向RSA的公钥交换过程。第三步逆向解密过程。在客户端代码中搜索Cipher.getInstance(AES/CBC/PKCS5Padding)这样的代码片段。找到解密函数用Frida Hook住打印出解密前的密文、使用的Key和IV以及解密后的明文。这是最直接有效的方法。案例还原假设我们Hook到了解密函数发现使用的是AES-128-CBC模式Key是1234567890abcdef16字节IV是abcdef1234567890。那么我们就可以用Python还原解密过程from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_response(encrypted_b64): encrypted_data base64.b64decode(encrypted_b64) key b1234567890abcdef iv babcdef1234567890 cipher AES.new(key, AES.MODE_CBC, iv) decrypted_padded cipher.decrypt(encrypted_data) decrypted unpad(decrypted_padded, AES.block_size) return decrypted.decode(utf-8) # 使用抓包得到的data字段 encrypted_data_b64 U2FsdGVkX12w7bT... print(decrypt_response(encrypted_data_b64))运行后我们很可能就得到了结构化的航班数据JSON。3.3 核心业务逻辑与数据模型推断解密之后我们拿到了原始数据。但这只是“数据”我们需要理解的是“信息”和“逻辑”。例如航班搜索的响应可能包含{ flights: [ { flightNo: MU5101, depTime: 2023-10-01 08:00, arrTime: 2023-10-01 10:20, price: 1200, discount: 0.85, cabinClass: Y, seatCount: 5, dynamicPriceTag: peak }, // ... 更多航班 ], recommendStrategy: time_priority, userTier: PLATINUM, adjustmentFactor: 0.95 }现在我们需要像侦探一样通过大量数据样本推断其业务逻辑动态定价模型收集同一航班在不同时间、不同用户、不同设备上的价格。分析price、discount、dynamicPriceTag、adjustmentFactor之间的关系。可以设计实验用新用户账号和老用户账号同时查询对比价格在不同时段凌晨 vs 傍晚查询。你可能会发现userTier为PLATINUM的用户总价会乘以adjustmentFactor0.95即享受95折。dynamicPriceTag为peak时基础价可能已经上浮。排序与推荐策略recommendStrategy字段直接提示了排序逻辑。但可能还有隐含策略。分析返回的航班列表顺序是否总是时间最短的排第一还是价格最低的或者是“时间优先”与“价格优先”的混合策略通过修改请求参数或许有sortBy参数来验证。用户画像影响userTier显然影响了价格。那么它从哪里来追溯登录接口的响应或者用户信息接口。这可能关联着用户的消费历史、会员等级等。逆向的目标是理解这个标签体系如何作用于后续的所有服务搜索、定价、改签费用等。库存与缓存逻辑seatCount显示剩余座位数。观察这个数字的变化频率。它是实时从数据库查询的还是缓存的可以尝试快速连续发起两次相同查询看seatCount是否立即变化。这有助于理解系统的数据一致性设计。这一阶段没有固定工具主要依靠数据分析能力和业务洞察力。将大量解密后的数据导入到Pandas DataFrame中进行聚合、统计、可视化是发现规律的好方法。4. 数据还原与模拟客户端构建当我们成功破解了签名、解密了响应、理解了核心逻辑后就可以着手“出数据”——即构建一个能模拟真实客户端行为、自动获取并解析目标数据的程序。4.1 构建请求链一个完整的“一条龙”服务往往需要多个接口按顺序调用。例如获取Token匿名接口或登录接口获取访问令牌access_token。获取密钥/会话参数可能有一个初始化接口返回当前会话的加密密钥或一些动态参数。业务查询使用Token和密钥构造签名发送业务请求如搜索。数据解析与后处理解密响应提取所需数据并根据推断的业务逻辑进行二次计算如应用会员折扣。我们需要用代码将这个链条串联起来并处理好各个环节的依赖关系。例如access_token通常有过期时间需要实现自动刷新机制。class EasternAirSpider: def __init__(self): self.session requests.Session() self.access_token None self.aes_key None self.aes_iv None self.user_tier None def login(self, username, password): # 1. 模拟登录可能先获取RSA公钥加密密码 # 2. 获取 access_token 和 user_tier # 3. 可能同时获取或触发获取动态AES密钥 pass def _generate_signature(self, params): # 根据逆向出的算法生成签名 sorted_params sorted(params.items()) sign_str .join([f{k}{v} for k, v in sorted_params]) sign_str fkey{self.sign_key} # sign_key 可能是固定的或动态的 return hashlib.md5(sign_str.encode()).hexdigest().upper() def _encrypt_request_data(self, data_dict): # 如果请求体也需要加密 json_str json.dumps(data_dict) cipher AES.new(self.aes_key, AES.MODE_CBC, self.aes_iv) padded pad(json_str.encode(), AES.block_size) encrypted cipher.encrypt(padded) return base64.b64encode(encrypted).decode() def _decrypt_response_data(self, encrypted_b64): # 解密响应数据 encrypted base64.b64decode(encrypted_b64) cipher AES.new(self.aes_key, AES.MODE_CBC, self.aes_iv) decrypted_padded cipher.decrypt(encrypted) decrypted unpad(decrypted_padded, AES.block_size) return json.loads(decrypted.decode()) def search_flights(self, dep_city, arr_city, dep_date): # 构造请求参数 params { depCity: dep_city, arrCity: arr_city, depDate: dep_date, timestamp: int(time.time() * 1000), nonce: .join(random.choices(abcdef0123456789, k8)) } params[signature] self._generate_signature(params) # 如果需要对请求体加密 # body self._encrypt_request_data({...}) # 否则直接发送JSON headers { X-Access-Token: self.access_token, Content-Type: application/json } resp self.session.post(https://api.ceair.com/v1/flight/search, jsonparams, headersheaders) resp_json resp.json() if resp_json[code] 0: encrypted_data resp_json[data] decrypted_data self._decrypt_response_data(encrypted_data) # 应用本地推断的业务逻辑如根据user_tier计算最终价格 for flight in decrypted_data[flights]: if self.user_tier PLATINUM: flight[finalPrice] flight[price] * flight.get(discount, 1) * decrypted_data.get(adjustmentFactor, 1) else: flight[finalPrice] flight[price] * flight.get(discount, 1) return decrypted_data else: raise Exception(fSearch failed: {resp_json[message]})4.2 数据持久化与监控构建出的爬虫或模拟客户端最终目的是为了持续、稳定地获取数据。这就需要考虑数据存储将解析后的结构化数据存入数据库如SQLite、MySQL或文件如JSON Lines、Parquet方便后续分析。错误处理与重试网络请求可能失败签名可能过期接口可能变更。代码中必须有完善的异常捕获、重试机制如指数退避和日志记录。反反爬应对目标系统可能有反爬虫机制如IP频率限制、请求指纹识别TLS指纹、浏览器指纹。这时可能需要使用代理IP池、模拟更真实的请求头如User-Agent、甚至使用playwright或selenium模拟浏览器环境。但务必谨慎评估法律风险。变更监控接口地址、参数、签名算法、加密密钥都可能更新。需要建立监控机制当数据获取失败时能快速判断是网络问题、密钥失效还是接口变更并触发重新逆向分析的流程。5. 逆向工程中的典型问题与排查实录在整个逆向过程中你会遇到无数坑。下面记录几个最典型的场景和我的解决思路。5.1 问题一抓不到HTTPS流量证书错误现象Charles配置好代理后手机App或浏览器提示网络错误Charles里看不到任何HTTPS请求。原因HTTPS需要中间人MITM解密必须在设备上安装并信任Charles的根证书。解决在Charles中帮助 - SSL代理 - 保存Charles根证书。得到一个.pem或.cer文件。将此证书发送到手机安装并务必在系统安全设置中将其标记为“受信任的凭据”对于Android可能还需要在“用户凭据”和“系统凭据”中都安装。iOS需要在“已下载的描述文件”中安装并在“关于本机-证书信任设置”中完全信任。在Charles的SSL代理设置中确保添加了目标域名如*.ceair.com到代理列表。5.2 问题二响应数据是乱码或无法解密现象成功解密后数据是乱码或者解密函数抛出Padding is incorrect异常。排查检查加密模式最常见的AES模式是CBC和ECB。你逆向出来的可能是CBC但实际用的是ECB不推荐但仍有使用。ECB模式不需要IV。用Frida Hook确认Cipher.getInstance传入的完整字符串。检查Key和IVKey和IV必须是正确的字节长度AES-128是16字节AES-256是32字节。确认从代码或Hook中获取的Key和IV值完全正确包括其编码是字符串还是16进制表示。有时Key会经过一次MD5或SHA256哈希后才被使用。检查填充方式PKCS5Padding和PKCS7Padding在AES上是等价的但NoPadding则不同。如果数据本身已经是块大小的整数倍但代码用了NoPadding而你用PKCS7去解就会出错。检查数据是否经过多重编码/压缩解密出的数据可能还不是JSON可能是Protobuf、MessagePack等二进制序列化格式或者经过了Gzip压缩。需要根据响应头Content-Encoding或数据魔数magic bytes来判断。5.3 问题三签名总是验证失败现象自己模拟生成的签名服务器永远返回signature invalid。排查参数顺序与大小写确认参数排序规则是按字母序ASCII升序吗Key的大小写是否敏感拼接时用的是还是amp;包含所有参数签名是否包含了URL查询字符串Query String还是只包含Body或者Header中的某些字段如X-Timestamp,X-Nonce也需要参与签名仔细对比多个真实请求找出所有参与签名的变量。空值处理值为空的参数是忽略还是拼接成key的形式密钥来源确认你使用的secretKey是正确的、最新的。它可能每小时变化一次需要从一个心跳接口定时获取。编码问题在拼接签名字符串前参数值是否需要先进行URL编码有时值中的空格、中文等特殊字符需要处理。5.4 问题四请求被风控返回403或数据为空现象模拟请求偶尔成功但频率一高就失败返回错误码或空数据。分析触发了服务器的反爬虫或风控策略。应对策略需谨慎评估降低请求频率在请求间加入随机延时如time.sleep(random.uniform(1, 3))。模拟更真实的请求头不仅要有User-Agent还要包含Accept-Language、Referer对于Web、X-Requested-With等。可以从真实浏览器或App的请求中完整复制一套。维护会话使用requests.Session()保持Cookie模拟一个真实用户的连续操作。处理验证码如果遇到验证码项目复杂度会急剧上升。可能需要引入图像识别如Tesseract OCR但效果通常不好或考虑商业打码平台但这已超出纯技术逆向的范畴且法律风险极高强烈不建议尝试。理解业务逻辑有时空数据是正常的业务响应如确实无航班。需要结合具体业务判断。核心避坑指南逆向工程是一场持久战耐心和细致比任何技巧都重要。一定要做好详尽的记录对每一个猜测都设计实验去验证。不要试图一次性理解整个系统而是将其分解为独立的、可验证的小模块如登录、搜索、解密逐个击破。最重要的是始终在合法合规的沙箱环境如自己搭建的测试后端、明确允许安全测试的平台中练习这些技术将技能用于正途例如自动化测试、第三方集成开发或安全加固。