Android自动化对抗实战:绕过大众点评风控的UI与网络层攻防
1. 项目概述与核心挑战最近在折腾一个自动化脚本核心需求是帮朋友自动报名参加大众点评上的“霸王餐”活动。这类活动通常需要用户手动在App里完成一系列点击、浏览、填写表单的操作耗时耗力。我的第一反应自然是想到用Auto.js这类基于Android无障碍服务的自动化工具来解放双手。然而事情远没有想象中简单。大众点评作为一款成熟的商业应用其反自动化、反脚本的机制业内常称为“风控”或“人机验证”做得相当严密直接录制回放或者写简单坐标点击的脚本几乎百分百会在短时间内触发限制轻则操作失败重则账号异常。这恰恰是本次实战最有价值的地方它不是一个简单的“Hello World”式自动化教程而是一场与App安全工程师的“攻防博弈”。我们需要深入理解大众点评部署了哪些防线并找到合理、可持续的绕过方法。整个过程涉及对Android应用运行时的观察、对网络请求的抓包分析、以及对自动化工具本身能力的深度运用。最终的目标是构建一个稳定、低调、能够模拟真人行为的自动化流程而不仅仅是实现功能。2. 逆向分析拆解大众点评的防御体系在动手编写绕过代码之前我们必须先当一回“侦察兵”搞清楚对手的布防情况。大众点评的防自动化机制是一个多层次、立体化的防御体系我通过静态观察和动态测试总结出以下几个核心层面。2.1 界面层检测超越坐标点击最基础的防御是针对纯坐标点击的脚本。大众点评的界面元素并非一成不变。动态ID与内容描述符很多关键按钮如“立即报名”、“提交”的resource-id并不是固定的字符串而是包含时间戳或随机后缀例如com.dianping.v1:id/btn_submit_1633024450000。单纯依靠ID定位会立即失效。此外一些元素的content-desc内容描述属性可能为空或也是动态生成的。非标准控件与自定义视图大量交互组件并非Android标准控件如TextView,Button而是自定义的View。这些自定义视图对无障碍服务Auto.js依赖的基础的支持程度不一有时className属性非常泛化如android.view.View无法用于精确定位。实操心得不要迷信id和className。在Auto.js中使用text()、desc()选择器结合正则表达式或者通过组件在屏幕上的相对位置关系父子节点、兄弟节点来定位往往更可靠。例如寻找包含“报名”文字的父级可点击组件。2.2 行为特征分析识别机器模式这是风控的核心。系统会监控用户的一系列交互行为建立模型来区分人和机器。操作时序与频率人类的操作有思考间隔点击之间会有毫秒级到秒级的不规则延迟。脚本如果使用固定的sleep(1000)其精准的、周期性的操作节奏极易被识别。滑动速度、轨迹的线性程度真人滑动会有加速度和微小抖动也是检测点。传感器数据模拟高级风控会检查设备传感器如加速度计、陀螺仪是否有数据。模拟器或某些自动化环境下的传感器数据可能是静止的或完全缺失。真实的手机在手持时这些传感器会有微小的、持续的噪声数据。应用使用环境上下文脚本通常只在前台运行目标App。而真实用户可能会接个电话、切到微信回个消息、锁屏再打开。单一、连续、高专注度的App使用会话是一个风险信号。2.3 客户端环境指纹检测大众点评App在启动和运行时会收集一系列设备参数生成一个唯一的“设备指纹”用于标识和追踪设备。模拟器与Root检测会检查是否存在/system/bin/su、/system/xbin/su等Root文件读取android.os.Build中的一系列属性如MODEL,MANUFACTURER,PRODUCT等并与已知的模拟器特征如Genymotion、夜神模拟器的特定MODEL值进行比对。在模拟器或已Root的设备上运行风险等级会显著提高。运行环境异常检测是否安装了Xposed、Frida、JustTrustMe等常见的逆向分析框架或证书锁定绕过工具。也会检查App是否被重打包签名校验。自动化框架特征虽然Auto.js本身通过无障碍服务实现相对隐蔽但一些特征仍可能暴露例如特定的辅助功能服务名称如org.autojs.autojspro:accessibility在运行。通过getRunningServices或getRunningTasks可能发现Auto.js应用在前台或后台活跃。检测Settings.Secure中是否启用了非系统原生的辅助功能服务。2.4 网络请求层加固即使前端操作模拟得天衣无缝网络请求层面也可能功亏一篑。参数签名与加密所有关键的业务请求如提交报名的URL参数或请求体Body很可能不是明文的。客户端会使用一个或多个密钥对参数可能包含时间戳、设备指纹、用户令牌等进行哈希如MD5、SHA256或对称加密如AES生成一个sign或token参数附在请求中。服务器收到请求后用同样的算法验签不一致则拒绝。这是防止直接构造HTTP请求进行滥用的关键手段。人机验证挑战在触发风控后可能会弹出图形验证码滑块、点选、文字识别或更复杂的验证。这些验证通常由第三方服务如极验、腾讯云验证码提供强行破解难度极高。3. 绕过策略设计与工具选型分析了防御体系我们就可以“对症下药”设计一套组合绕过策略。核心思想是尽可能模拟真实用户和真实设备避免触发任何一层风控的阈值。3.1 核心策略环境模拟与行为拟人化1. 使用真实物理手机放弃模拟器使用一台未Root的、品牌常见的安卓手机如小米、华为的中端机型。这是降低设备指纹风险最有效的方法。确保手机传感器正常工作。2. 修改Auto.js运行特征需Root环境高风险如果必须在特定环境如已Root的手机下运行可以考虑使用Magisk模块如“隐藏Magisk”、“应用变量”或Xposed模块如“设备指纹模拟器”来对大众点评App隐藏Root状态、修改设备报告给App的Build信息甚至隐藏Auto.js辅助服务的存在。但请注意此操作复杂且有变砖或封号风险仅适用于深度测试不推荐普通用户操作。3. 拟人化操作逻辑*随机延迟用random()函数生成随机等待时间例如sleep(1000 random(0, 500))模拟操作间隔的不确定性。 *非精准点击不要总是点击控件的中心点。可以获取控件坐标后在其范围内随机偏移几个像素再点击。 *模拟滑动使用gesture()函数模拟带加速度的滑动而不是简单的两点间直线swipe。可以设计一个函数让滑动的速度先加速后减速轨迹中加入微小的随机偏移。 *模拟背景切换在脚本执行过程中随机地、短暂地按一下Home键返回桌面或者打开一下系统设置再返回模拟用户被中断的场景。3.2 网络请求分析抓包与签名破解要绕过签名必须先分析签名算法。这里需要用到网络抓包工具。工具选型HTTP抓包Charles或Fiddler。需要在手机上安装并信任它们的CA证书并设置手机代理。这是分析HTTP/HTTPS请求和响应的标准工具。高级动态分析如果请求被加密或加固可能需要更强大的工具。Packet Capture免Root抓包App有时可以绕过简单的证书锁定。对于深度逆向可能需要使用Frida进行运行时Hook直接拦截加密函数的前后参数但此方法技术门槛极高。操作流程在电脑上启动Charles配置好代理如192.168.1.100:8888。在手机上配置Wi-Fi代理指向Charles。在手机浏览器访问chls.pro/ssl下载并安装Charles根证书。在手机设置中为证书授予信任对于高版本Android可能需要将证书安装到系统凭据这通常需要Root。打开大众点评App手动完成一次完整的霸王餐报名流程。在Charles中观察产生的所有网络请求重点关注报名提交的那个POST请求。查看其URL、Headers尤其是User-Agent,Cookie,Authorization等和请求体Body。签名分析实战假设你抓到的提交请求Body是一个JSON里面除了明文的表单数据如activity_id,user_info还有一个关键的signature字段。你需要对比多次请求用不同账号或稍后时间重复操作抓取多个请求。观察哪些参数是变化的如timestamp,nonce哪些是不变的。猜测算法常见的签名算法是将所有参数按特定顺序如字典序拼接成一个字符串然后加上一个密钥secret最后进行MD5或SHA256哈希。这个secret被硬编码在App的so库或Java代码中。验证猜测难点你可以尝试写一个Python脚本用你猜测的算法和参数去计算签名与抓包得到的签名对比。如果一致恭喜你找到了算法。如果不一致说明算法更复杂可能涉及对参数值的二次编码或者密钥是动态获取的。这时逆向分析App的源代码使用jadx-gui反编译APK或使用Frida进行动态调试就成为必要手段。这已进入深水区需要扎实的逆向工程基础。重要提示直接破解并公开商业App的签名算法可能涉及法律风险。本实战的目的在于学习逆向思维和自动化测试方法请勿用于恶意刷单、薅羊毛等破坏平台规则的行为。3.3 Auto.js代码实现稳健的元素定位与操作鉴于直接破解网络签名难度大、风险高我们优先追求在UI自动化层面做到极致拟真和稳定。以下是用Auto.js Pro版本差异不大编写核心模块的示例。模块一智能元素查找器// 智能查找文本控件支持模糊匹配和等待 function findTextWithRetry(text, retryTimes 10, interval 1000) { for (let i 0; i retryTimes; i) { let target textMatches(new RegExp(.*${text}.*)).findOne(500); // 使用正则模糊匹配 if (target) { console.log(第${i1}次尝试找到文本: ${text}); // 尝试找到其可点击的父级或自身 let clickableTarget target.clickable() ? target : target.parent().clickable() ? target.parent() : null; if (clickableTarget) { return clickableTarget; } else { // 如果找到的文本不可点击返回元素用于后续坐标点击 return target; } } sleep(interval random(200, 800)); // 随机等待 } console.error(重试${retryTimes}次后未找到文本: ${text}); return null; } // 通过兄弟节点和父子关系定位 function findButtonByContext(buttonText, contextText) { // 先找到上下文文本 let context textContains(contextText).findOne(5000); if (context) { // 在其兄弟节点或附近查找按钮 let button context.parent().findOne(textMatches(new RegExp(.*${buttonText}.*))); if (button button.clickable()) { return button; } } return null; }模块二拟人化操作库// 模拟人类点击在控件区域内随机点 function humanClick(uiObject) { if (!uiObject) { console.error(传入的UI对象为空); return false; } let bounds uiObject.bounds(); let centerX bounds.centerX(); let centerY bounds.centerY(); // 在控件中心点附近随机偏移(-5 ~ 5像素) let targetX centerX random(-5, 5); let targetY centerY random(-5, 5); // 加入按下和抬起之间的微小延迟 press(targetX, targetY, random(50, 120)); console.log(拟人点击坐标: (${targetX}, ${targetY})); return true; } // 模拟人类滑动带加速度曲线 function humanSwipe(startX, startY, endX, endY, duration) { let steps duration / 5; // 每5毫秒一个点 let points []; for (let i 0; i steps; i) { let t i / steps; // 使用缓动函数模拟加速度这里用简单的二次缓入缓出 let easeT t 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t 2, 2) / 2; let x startX (endX - startX) * easeT; let y startY (endY - startY) * easeT; points.push([x, y]); } gesture.apply(null, [duration].concat(points)); console.log(拟人滑动从(${startX},${startY})到(${endX},${endY})时长${duration}ms); }模块三主流程逻辑以进入活动页面并报名为例// 启动App launchApp(大众点评); sleep(3000 random(0, 2000)); // 1. 处理可能的弹窗更新、广告 let closeBtn findTextWithRetry(关闭|跳过|我知道了, 3, 1500); if (closeBtn) { humanClick(closeBtn); sleep(1000 random(0, 800)); } // 2. 寻找并进入“免费试”或“霸王餐”入口 // 假设入口在底部Tab的“我的”页面里 let myTab findTextWithRetry(我的, 5, 1500); if (myTab) { humanClick(myTab); sleep(2000 random(0, 1000)); // 滑动查找“免费试”入口 for (let i 0; i 5; i) { let freeTrialEntry findTextWithRetry(免费试|霸王餐, 2, 1000); if (freeTrialEntry) { humanClick(freeTrialEntry); break; } // 没找到就向下滑动 humanSwipe(device.width / 2, device.height * 0.7, device.width / 2, device.height * 0.3, 500); sleep(1500 random(0, 800)); } } // 3. 在活动列表页随机选择或查找特定活动 sleep(3000); // 假设通过活动标题关键词查找 let targetActivity findTextWithRetry(火锅|自助餐|蛋糕, 10, 2000); // 用你感兴趣的关键词 if (targetActivity) { humanClick(targetActivity); sleep(3000 random(0, 1500)); // 等待活动详情页加载 // 4. 查找并点击“立即报名”按钮 let applyBtn findTextWithRetry(立即报名|马上申请, 10, 1500); if (applyBtn) { // 在点击前模拟阅读页面的随机滑动 for (let j 0; j random(2, 5); j) { humanSwipe(device.width / 2, device.height * 0.6, device.width / 2, device.height * 0.4, random(400, 800)); sleep(random(800, 2000)); } humanClick(applyBtn); console.log(已点击报名按钮); sleep(5000); // 等待后续页面或结果 // 5. 处理可能的表单填写姓名、电话等 // 这里需要根据实际表单UI调整定位方式 let nameField findTextWithRetry(姓名|称呼, 5, 1000)?.parent()?.findOne(className(EditText)); if (nameField) { nameField.setText(你的姓名); sleep(500 random(0, 300)); } // ... 填写其他字段 // 6. 查找并点击最终提交按钮 let finalSubmitBtn findTextWithRetry(提交报名|确认提交, 5, 1500); if (finalSubmitBtn) { humanClick(finalSubmitBtn); console.log(报名信息已提交); } } else { console.error(未找到报名按钮可能活动已结束或条件不符); } } else { console.error(未找到目标活动); }4. 高级绕过技巧与深度伪装如果上述方法仍然被检测到说明风控升级了我们需要考虑更底层的伪装。4.1 对抗运行时检测隐藏Auto.js进程这非常困难。一种思路是使用Android的WorkManager或JobScheduler在后台调度任务但Auto.js脚本的交互特性使其难以完全后台化。更激进的方法是将脚本逻辑移植到通过adb shell input命令控制的PC端方案但这失去了Auto.js的环境便利性。模拟传感器数据可以通过Android的SensorManager模拟注入数据但这需要系统级权限如Root后使用setprop或修改系统文件普通App无法做到。一个取巧的办法是在运行脚本时轻微晃动手机让真实的传感器产生数据。修改系统属性需Root使用Magisk模块或直接修改/system/build.prop风险极高来改变设备上报的ro.product.model,ro.product.manufacturer等属性使其更像一台普通手机。4.2 网络请求拦截与重放MitM如果我们成功分析了签名算法就可以在Auto.js中直接构造合法的HTTP请求完全跳过UI操作。这需要使用Auto.js的http模块或okhttp3库。// 假设我们已经逆向出了签名函数 sign(params) function generateSign(params) { // 这里是逆向出来的签名算法例如 let secret dianping_secret_2023; // 这个密钥需要从App中逆向得到 let str Object.keys(params).sort().map(k ${k}${params[k]}).join(); str key secret; return md5(str); // 需要一个md5函数 } function submitApplicationViaHttp(activityId, userToken) { let url https://api.dianping.com/v1/activity/apply; // 示例URL let timestamp new Date().getTime(); let params { activity_id: activityId, user_token: userToken, timestamp: timestamp, nonce: Math.random().toString(36).substr(2, 15), device_id: 你的设备指纹, // ... 其他必要参数 }; params.sign generateSign(params); let response http.postJson(url, params, { headers: { User-Agent: Mozilla/5.0 (Linux; Android 10; MI 9) ..., Content-Type: application/json, Cookie: 你的Cookie } }); if (response.statusCode 200) { let result response.body.json(); console.log(HTTP提交结果:, result); return result.code 0; // 假设0为成功 } else { console.error(HTTP请求失败:, response.statusCode, response.body.string()); return false; } }关键点device_id,user_token,Cookie这些都需要从App的本地存储或前期UI自动化登录流程中获取。Cookie通常具有时效性需要维护会话。4.3 应对人机验证如果触发了滑块或点选验证码在Auto.js中解决非常困难。可以考虑以下策略降级触发概率通过极致的拟人化操作和降低请求频率尽量避免触发验证。人工干预兜底在脚本中设置检查点如果检测到验证码界面通过识别特定图片或文字则通过toast、console或网络通知提醒用户脚本暂停等待用户手动完成验证后脚本再继续执行。接入打码平台外部服务当出现验证码时脚本截图将图片发送到第三方打码平台如超级鹰、图鉴进行识别然后回传坐标让脚本操作。这需要额外的费用和网络请求增加了复杂度和不确定性。5. 实战问题排查与稳定性优化在实际运行中你会遇到各种各样的问题。以下是我踩过的一些坑和解决方案。5.1 常见问题速查表问题现象可能原因排查与解决思路脚本运行后无任何反应Auto.js无障碍服务未开启检查手机设置-无障碍服务中是否已启用对应的Auto.js服务。找不到“立即报名”按钮1. 页面未加载完成2. 元素定位器失效3. 控件在屏幕外1. 增加sleep或使用waitForActivity。2. 使用boundsInScreen()检查控件位置使用hierarchy查看当前UI树。3. 先滑动到控件可能出现的区域。点击无效无响应1. 点击坐标错误2. 控件非真正可点击3. 被透明层遮挡1. 使用humanClick函数并打印坐标确认。2. 尝试点击其父控件或使用click(centerX, centerY)。3. 检查是否有弹窗如“去评价”未关闭。操作几次后App无响应或闪退触发了行为风控1.大幅降低操作频率在关键步骤间插入长随机延迟5-10秒。2. 模拟更多随机行为如切换App、锁屏。3. 考虑更换账号或设备。报名提交后提示“网络错误”或“活动太火爆”1. 网络请求签名错误2. 请求频率过高被限流3. 账号已被标记1. 确认是否必须走HTTP接口。如果是需精准逆向签名。2. 每个账号每天限制报名次数需遵守规则。3. 暂停使用该账号检查账号状态是否正常。出现图形验证码风控等级提升判定为可疑操作1.立即停止当前脚本。2. 让账号“冷静”一段时间数小时或隔天。3. 后续运行时将操作节奏放得更慢更像真人。5.2 稳定性优化技巧增加冗余校验在关键操作如点击报名按钮前不仅检查目标元素存在还可以检查其enabled和selected状态甚至截图保存当前界面用于事后分析。实现状态机将脚本流程设计成状态机每个状态如“首页”、“我的页面”、“活动列表”、“详情页”、“报名页”都有明确的进入条件和退出动作。这样当脚本意外中断或页面跳转异常时更容易恢复或重试。日志与监控将关键步骤、找到的元素坐标、网络请求结果等详细记录到文件或控制台。可以设置简单的HTTP服务器让脚本在遇到验证码等阻塞问题时发送通知到你的电脑或手机。账号轮换与冷却如果有多账号需求设计账号池和冷却时间机制。一个账号完成操作后休眠足够长的时间例如几小时再使用避免同一设备频繁切换账号。环境隔离为自动化脚本准备专用的手机或手机分身/多开环境与日常使用的主账号物理隔离降低主账号被连带处罚的风险。这场与大众点评防自动化机制的“攻防”实践让我深刻体会到现代App的风控是一个综合性的系统工程。纯粹的UI自动化在面对精心设计的防御时显得力不从心必须结合逆向分析、网络协议理解、环境伪装等多方面知识。成功的绕过不是寻找一个一劳永逸的“漏洞”而是构建一个在行为模式、设备指纹、网络请求上都无限接近真人的模拟过程。这个过程本身就是对Android应用安全、逆向工程和自动化测试技术的一次深度学习和综合应用。最后务必牢记技术应用于学习和提升效率应当合规合法尊重平台规则避免对他人服务造成不当影响。