安居客App逆向分析:从抓包到参数签名算法还原实战

📅 2026/6/26 6:28:15
安居客App逆向分析:从抓包到参数签名算法还原实战
1. 项目概述从安居客App数据流中寻找“钥匙”最近在分析一些房产数据时不可避免地会接触到安居客这类头部平台。直接通过网页端获取数据往往会遇到反爬机制越来越严格的问题比如复杂的验证码、请求频率限制甚至动态加载的数据难以直接抓取。这时候很多开发者会把目光转向移动端App。App作为平台的核心客户端其与服务器通信的API接口往往是获取结构化数据最直接、最稳定的途径之一。因此对安居客App进行逆向分析理解其关键请求参数的生成逻辑就成了一个非常实际且有价值的课题。这个项目的核心目标就是深入安居客App的内部解析其网络请求特别是那些用于身份验证和数据签名的关键参数比如你提到的qn-mhp。这不仅仅是“破解”一个参数那么简单它涉及到对App整体通信安全机制的理解。我们需要搞清楚App是如何证明“我是我”的服务器又是如何校验请求的合法性与完整性的这个过程就像是在复杂的数字迷宫中找到那把打开数据之门的“钥匙”。掌握了参数生成的原理我们就能模拟出合法的请求从而稳定、高效地获取所需的房产列表、房源详情等数据为后续的数据分析、市场研究或竞品监控提供可靠的数据源。无论你是数据爱好者、市场分析师还是对移动安全感兴趣的开发者这个过程都能让你对现代App的通信架构有更深刻的认识。2. 逆向工程环境与工具链搭建工欲善其事必先利其器。进行App逆向分析一套趁手的工具链是成功的一半。这里我分享一套经过实战检验的、以Android平台为主的组合方案。需要注意的是整个过程应在拥有合法授权的设备上进行仅用于学习与研究目的。2.1 核心抓包与调试环境网络请求捕获是整个分析的起点。我强烈推荐使用mitmproxy作为主力抓包工具。它是一个基于Python的命令行HTTP/HTTPS代理功能强大且可脚本化扩展。相比图形化的Fiddler或Charlesmitmproxy在处理大量请求、自动化测试和深度定制方面更具优势。你需要在一台电脑作为代理服务器上安装并运行它。为了让App的流量经过我们的代理必须在测试设备上安装mitmproxy的CA证书。对于Android设备通常需要将证书安装到系统信任的凭据存储中这可能需要Root权限。如果设备未Root可以尝试将证书安装到用户凭据存储但部分App尤其是高版本Android上的应用可能不会信任用户证书这就需要更复杂的绕过方案例如使用Magisk模块如MagiskTrustUserCerts或将证书打包进系统。这是逆向过程中可能遇到的第一个坎。除了代理一个动态调试环境也至关重要。对于Android AppJadx是反编译APK文件、查看Java/Kotlin源码的利器。它能将Dex文件高效地转换为可读性很高的Java代码。而对于潜在的Native层C/C代码分析则需要用到IDA Pro或开源的Ghidra。Frida则是动态插桩的“瑞士军刀”它允许你在App运行时注入自己的脚本拦截函数调用、修改参数、打印调用栈等是验证静态分析猜想、追踪加密函数执行路径的终极武器。2.2 针对安居客App的专项配置安居客作为一款用户量巨大的商业应用必然会采用各种加固和混淆技术来保护其代码和通信安全。因此我们的工具链需要针对性地进行配置。首先你拿到的安居客APK很可能经过了商业加固如腾讯乐固、360加固等。直接使用Jadx打开可能会看到大量无意义的混淆代码或者核心逻辑被隐藏。这时你需要先进行脱壳。对于不同的加固方案脱壳方法也不同。有些可以通过特定版本Frida脚本在内存中Dump出解密后的Dex文件有些则需要利用加固方案的历史漏洞。这个过程需要一定的经验和耐心也是逆向中技术含量较高的部分。我建议先从一些公开的脱壳工具或脚本仓库如github上的一些项目开始尝试并密切关注安全社区的最新动态。其次由于我们要分析的是网络请求需要确保mitmproxy能成功捕获到HTTPS流量。除了安装证书还要注意App可能启用了证书绑定SSL Pinning。这意味着App只信任自己内置的特定证书而拒绝我们代理的证书。对付证书绑定通常有两种思路一是使用Frida脚本在运行时Hook掉证书验证的相关函数如OkHttp的CertificatePinner或Android系统的TrustManager使其验证失效。网上有大量现成的通用脚本可供参考修改。二是如果App使用了Xposed框架可用的模块也可以尝试使用JustTrustMe这类模块但Frida的方案更为灵活和通用。注意所有工具的使用和修改都应遵守相关法律法规和服务条款。逆向分析的目的应是理解技术原理、提升安全能力而非进行未授权的数据爬取或破坏性操作。3. 安居客网络请求深度抓取与初步观察配置好环境后我们就可以开始实战了。启动mitmproxy将手机代理设置好然后打开安居客App进行一些典型操作比如搜索某个城市的二手房、翻页、查看房源详情等。mitmproxy的控制台会滚动显示所有的HTTP/HTTPS请求。3.1 关键API接口识别在一大堆请求中可能包含图片、前端资源、统计上报等我们需要快速定位到核心的数据API。通常这类API的路径Path会包含明显的业务关键词如/house/list、/ershoufang/search、/api/v5/property/detail等。响应内容一般是JSON格式结构清晰包含了房源列表、详情信息等。找到目标API后重点观察其请求头Headers和请求体Body。除了常见的User-Agent、Content-Type外你需要特别留意那些看起来是自定义的、或者名称比较“奇怪”的头部字段。例如你提到的qn-mhp就极有可能是这样一个关键参数。此外还可能有类似X-Sign、X-Timestamp、Authorization、X-Client-ID等字段。把这些可疑的参数名全部记录下来。3.2 参数规律性分析接下来我们需要进行多次重复操作如多次搜索同一条件、翻页并对比同一个API请求的参数变化。使用mitmproxy的“Repeat”功能或者写个简单的Python脚本重放请求都很方便。对比时关注以下几点哪些参数是固定的比如设备标识符、App版本号等这些通常是一次性生成或从配置中读取的。哪些参数是随着请求内容变化的比如搜索关键词、分页页码、地理位置坐标等这些是业务参数。哪些参数是每次请求都变且看起来无规律的比如qn-mhp、X-Sign等这些就是我们需要重点攻破的签名或令牌参数。它们很可能由“固定部分业务参数时间戳密钥”通过某种算法如HMAC、AES、RSA或自定义哈希生成用于防止请求被篡改和重放。你可以尝试将不同请求的qn-mhp值记录下来观察其长度、字符集是否是Base64编码。尝试修改业务参数如把页码从1改成2但不修改qn-mhp然后重放请求服务器很可能会返回签名错误如HTTP 403或特定的错误码。这证实了该参数在请求验证中的核心地位。4. 静态代码分析与关键逻辑定位在动态抓包获得足够线索后我们需要深入代码内部寻找生成这些关键参数的函数。这就是静态分析大显身手的时候。4.1 使用Jadx进行全局搜索与入口定位用Jadx打开已脱壳的安居客APK。首先利用其强大的全文搜索功能。直接搜索我们怀疑的参数名比如“qn-mhp”。搜索结果可能会显示这个字符串常量在代码中的哪些地方被引用。通常它可能出现在网络请求库的拦截器Interceptor中如果App使用OkHttp或Retrofit很可能会有一个自定义的Interceptor来统一添加请求头。搜索“Interceptor”、“addHeader”、“qn-mhp”等关键词。某个网络请求工具类或辅助类中专门负责参数组装和签名的类。JNIJava Native Interface调用附近如果加密逻辑在Native层Java代码中可能会有一个本地方法声明native关键字其方法名附近可能会有关于参数名的注释或字符串。找到引用点后点击进入Jadx会尝试进行反编译。你可能会看到类似这样的代码片段此为示例非真实代码public class SignUtils { public static String generateMhp(MapString, String params, String timestamp, String deviceId) { // ... 参数排序、拼接 ... String signStr sortAndConcat(params) timestamp deviceId SECRET_KEY; // ... 调用某个加密方法 ... return encrypt(signStr); } }或者在OkHttp的拦截器中Override public Response intercept(Chain chain) throws IOException { Request originalRequest chain.request(); String mhp calculateMhp(originalRequest); Request newRequest originalRequest.newBuilder() .header(qn-mhp, mhp) .build(); return chain.proceed(newRequest); }4.2 追踪加密函数与算法识别一旦定位到生成qn-mhp的函数比如calculateMhp或generateMhp就顺着调用链往下分析。这个函数内部可能直接包含了加密逻辑也可能调用了其他工具方法。你需要关注字符串操作参数是如何拼接的是否按照键Key的字母顺序排序是否在拼接的字符串前后加了特定的前缀或后缀如prefix data suffix加密API调用代码中是否出现了MessageDigestMD5, SHA-1, SHA-256、MacHmacSHA256、CipherAES, RSA等Java加密类这些是明确的算法指示。第三方库是否引入了像CryptoJS的Java移植版、或某些商业SDK这可能需要额外分析库的用法。Native调用如果看到native方法声明如public static native String encryptNative(String data);那么核心逻辑就在so动态链接库文件中。你需要记下这个JNI函数名如Java_com_anjuke_util_SignHelper_encryptNative然后转到IDA Pro或Ghidra中分析对应的so文件。在静态分析中代码混淆会增加难度。类名、方法名、变量名可能变成a,b,c,a1,b2这种无意义的字符。这时候你需要结合上下文逻辑比如它处理了哪些数据、调用了哪些已知的API和动态调试来推断其真实功能。5. 动态调试与算法验证静态分析给出了“地图”动态调试则是我们行走在程序实际执行路径上的“导航”。Frida在这里扮演了无可替代的角色。5.1 使用Frida进行函数Hook假设我们通过静态分析怀疑com.anjuke.security.SignGenerator类下的makeSignature方法是生成qn-mhp的关键。我们可以编写一个Frida脚本Java.perform(function() { var SignGenerator Java.use(com.anjuke.security.SignGenerator); // Hook makeSignature方法 SignGenerator.makeSignature.overload(java.lang.String, java.util.Map).implementation function(param1, paramMap) { console.log([] makeSignature called!); console.log( param1: param1); // 可能是时间戳或固定字符串 console.log( paramMap: JSON.stringify(mapToStringObject(paramMap))); // 打印Map内容 // 调用原方法获取结果 var result this.makeSignature(param1, paramMap); console.log( result (qn-mhp): result); // 打印调用栈帮助定位调用来源 console.log(Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Exception).$new())); return result; }; // 辅助函数将Java Map转换为JS可JSON化的对象 function mapToStringObject(map) { var obj {}; var iterator map.keySet().iterator(); while (iterator.hasNext()) { var key iterator.next(); obj[key] map.get(key).toString(); } return obj; } });将这个脚本通过Frida注入到正在运行的安居客App进程中。然后在App里触发一次网络请求。如果Hook成功你将在Frida的控制台看到详细的输入参数和输出结果。这直接验证了我们的猜想并拿到了清晰的输入输出对应关系。5.2 参数构造过程还原与算法复现通过多次Hook收集不同请求下的输入和输出。例如请求1输入param1“1648793600”,paramMap{“city”: “sh”, “page”: “1”}输出mhp“abc123...请求2输入param1“1648793605”,paramMap{“city”: “sh”, “page”: “2”}输出mhp“def456...”对比这些数据结合之前静态分析看到的可能逻辑如排序、拼接我们可以尝试在本地用Python还原这个算法。过程可能如下参数排序与拼接将paramMap中的所有键值对按照键的字典序排序然后拼接成key1value1key2value2...的字符串。拼接其他要素将排序后的参数字符串、param1时间戳、可能还有一个固定的设备ID或Token、以及一个密钥SECRET拼接起来。这个密钥是静态分析中难以直接获取的可能硬编码在代码里经过混淆也可能从服务器动态获取。执行哈希/加密对拼接后的整个字符串执行某种哈希如HMAC-SHA256或加密操作。编码输出将二进制结果进行Base64或Hex编码得到最终的qn-mhp值。如何获取密钥这是最难的一步。密钥可能以字符串常量形式存在但被分割、混淆或简单加密。通过多个字符串拼接、或经过简单变换如异或后得到。在App启动时从服务器获取并缓存到本地。这时需要Hook网络请求或特定的初始化函数。你可以尝试在Jadx中搜索与“secret”、“key”、“salt”、“auth”相关的字符串常量。或者在Frida中Hook所有String的初始化或相关工具类的初始化方法观察运行时产生的字符串。5.3 本地Python算法复现示例假设我们最终推断出算法是qn-mhp HMAC-SHA256(排序后的参数字符串 timestamp, SECRET_KEY).hexdigest()。那么Python复现代码可能如下import hashlib import hmac import time from urllib.parse import urlencode def generate_qn_mhp(params: dict, timestamp: str, secret_key: str) - str: 模拟生成安居客 qn-mhp 参数 params: 业务参数字典如 {city: sh, page: 1} timestamp: 时间戳字符串 secret_key: 密钥需要从逆向中获取 # 1. 对业务参数按key进行字典序排序并拼接 sorted_params sorted(params.items(), keylambda x: x[0]) param_str urlencode(sorted_params) # 输出如 cityshpage1 # 2. 拼接时间戳假设拼接在参数后面 sign_string param_str timestamp # 3. 使用HMAC-SHA256计算签名 # 注意secret_key需要转为bytessign_string也需要encode hmac_obj hmac.new(secret_key.encode(utf-8), sign_string.encode(utf-8), hashlib.sha256) signature hmac_obj.hexdigest() # 或者可能是base64编码 hmac_obj.digest().b64encode() return signature # 示例使用密钥是假设的需要逆向获取真实值 my_params {city: sh, page: 1} current_timestamp str(int(time.time())) my_secret your_reversed_secret_key_here # 此处需替换为真实密钥 mhp generate_qn_mhp(my_params, current_timestamp, my_secret) print(f生成的 qn-mhp: {mhp})6. 完整请求模拟与数据抓取实践在成功复现了qn-mhp或其他关键参数的生成算法后我们就可以用Python的requests库来模拟完整的App请求了。6.1 构建请求头与参数你需要完整地复制App请求中的Headers。除了我们逆向出来的签名头以下头部也至关重要服务器可能会校验User-Agent: 需要使用移动端的UA例如Dalvik/2.1.0 (Linux; U; Android 10; ...)或App自定义的UA。Content-Type: 通常是application/json或application/x-www-form-urlencoded。X-Client-ID/Device-ID: 设备标识符可能是一个固定的UUID需要从App的首次请求或本地存储中提取。Authorization: 如果涉及用户登录可能是Bearer Token。X-Timestamp: 时间戳需要和签名中使用的时间戳一致。业务参数Body或Query Params则根据API文档我们逆向出来的规律来构造。6.2 请求流程编排与错误处理编写一个完整的请求函数其流程如下准备业务参数。获取当前时间戳。调用我们复现的签名函数生成qn-mhp。组装完整的请求头和请求体。发送HTTP请求。处理响应检查状态码。如果成功200解析JSON数据。如果失败如403签名错误、429频率限制根据错误信息进行调试检查时间戳同步、参数顺序、密钥是否正确、是否缺少必要头部。时间戳同步问题服务器的时间可能和本地时间有微小偏差。如果签名包含时间戳且服务器校验时间窗口很窄如±30秒就需要确保本地时间准确或者从服务器响应中获取时间有些API会在响应头里返回服务器时间。频率限制与IP封禁模拟请求时务必加入合理的延时如time.sleep(random.uniform(1, 3))避免请求过快触发服务器的反爬机制导致IP被暂时封禁。可以考虑使用代理IP池来分散请求。6.3 数据解析与存储成功获取到的数据通常是JSON格式。使用Python的json库解析后按需提取字段。例如对于房源列表你可能需要房源ID、标题、总价、单价、面积、户型、朝向、楼层、小区名、区域、发布时间、图片URL等。存储可以选择多种方式JSON文件简单直接适合小批量数据。CSV文件便于用Excel打开和分析。数据库SQLite/MySQL/PostgreSQL适合大规模、结构化存储和复杂查询。使用sqlite3或SQLAlchemy等库。MongoDB如果JSON结构非常复杂或多变NoSQL数据库更灵活。7. 逆向过程中的常见陷阱与应对策略在整个逆向安居客参数的过程中我踩过不少坑这里总结几个最常见的难题和解决思路。7.1 加固与混淆的对抗问题代码被严重混淆类名方法名全是a.b.c逻辑被隐藏或调用链被打乱。策略脱壳是第一要务确保拿到的是可分析的Dex。多尝试不同的脱壳工具和Frida内存Dump脚本。动态分析优先当静态分析无从下手时直接用Frida去Hook那些被频繁调用的、或参数/返回值是字符串的函数如StringBuilder.toString(),JSONObject.toString()从运行时数据倒推逻辑。搜索特征字符串即使类名混淆代码中出现的API域名、路径、固定的错误信息字符串是不会变的。以这些字符串为锚点定位关键代码区域。7.2 密钥的隐藏与变换问题找不到明显的密钥字符串或者找到的字符串直接用于计算签名不对。策略Hook 加密函数入口用FridaHookjavax.crypto.Mac.getInstance()、MessageDigest.getInstance()等打印传入的密钥SecretKeySpec的密钥字节数组。这是获取运行时密钥最直接的方法。追踪字符串生成密钥可能是由多个片段拼接而成。HookStringBuilder.append()或字符串的操作观察相关代码段。算法识别如果密钥被编码如Base64先解码再使用。有时密钥会与某个固定值进行异或XOR运算需要分析其变换逻辑。7.3 请求链依赖与上下文问题单独调用某个API失败提示“缺少上下文”或“会话失效”。策略模拟完整会话App的请求往往不是孤立的。可能需要先模拟一个“启动”或“初始化”请求获取一个全局的token或session_id后续请求都要带上。顺序请求有些API需要前一个API的返回结果作为参数。仔细分析App的正常操作流程用抓包工具查看请求序列并完整模拟这个序列。状态保持使用requests.Session()来维持Cookie和部分Header模拟App的状态保持机制。7.4 算法更新与风控升级问题今天还能用的脚本明天就失效了返回签名错误。策略关注App更新算法的改变通常伴随着App版本的更新。保留不同版本的APK以便回滚分析。设计可配置的签名模块将密钥、算法步骤、拼接顺序等写成可配置项便于快速调整。监控与告警自动化脚本应包含对响应状态的监控一旦大量失败能及时通知人工检查。逆向工程是一场与开发者的“猫鼠游戏”。安居客这样的平台会持续升级其安全策略。因此理解原理比掌握某一个固定算法更重要。掌握了静态分析、动态调试、算法还原这一套方法论你就能适应不断变化的技术挑战。最后再次强调所有技术都应在法律和道德框架内使用尊重数据所有权和平台规则将重点放在技术原理的学习和钻研上。