前段时间接了个需求要对一款涉嫌违规的体育竞猜类安卓样本做安全评估说白了就是从技术层面拆穿这类黑灰产App的底牌它怎么诱导用户、怎么和后端通信、加密怎么做的、能不能抓到有效数据用于风控识别。整个过程走下来反而是把安卓逆向、开发、协议分析三块知识串起来了。这篇文章就把完整思路、步骤和踩过的坑写出来给想入门安卓逆向的朋友当一条可复现的路径。先说清楚立场这类样本分析只用于安全防护、黑灰产识别和合规风控坚决反对一切赌博行为。技术本身是中性的怎么用就看目的。下面是实操全记录。1. 整体思路拆解为什么逆向分析是最快的学习路径很多新人学安卓逆向一上来就啃《Android软件安全与逆向分析》结果被ELF格式、ART虚拟机、smali语法劝退。实际上逆向分析一个真实样本比看书效率高得多尤其是分析这类涉赌App它集齐了你能遇到的大多数典型对抗手段加壳、反调试、root检测、HTTPS证书校验、Token签名、Native层加密算法甚至还有多开防检测。搞懂一个样本等于把移动安全主流的攻防点全过了一遍。整个分析流程可以分成四层静态拆解拿到App本体和资源Java层反编译找到逻辑入口动态Hook验证调用链协议抓包还原网络通信。每层都有对应的工具链和重点顺序不能乱因为上层的结果会直接决定下层的分析目标。我用的样本是一个虚构的“某体育比分竞猜类恶意样本”包名伪装成普通工具类应用图标却是足球相关。这类App通常会有十几MB到几十MB的体积说明里面塞了不少壳和so库。起步阶段先把apk当作一个zip压缩包用解压工具看内部结构重点看有没有lib目录下的so文件、assets目录下的可疑加密数据、classes.dex是原生的还是被加固后的。这个项目最妙的地方在于你不需要先学会全部理论你只需要带着一个明确的问题去翻代码这个App的服务器地址是什么、参数怎么加密的、有没有支付通道。目标越具体信息过滤效率越高。我习惯把整个分析过程用一个“黑盒转白盒”的逻辑串起来先黑盒跑起来看行为再白盒读代码看实现最后灰盒用Hook和抓包交叉验证三步走完基本能还原整个通信模型。2. 静态分析实操从APK解包到定位关键逻辑2.1 第一步拆解APK看清文件结构拿到apk后不要急着上工具先用 unzip 解包看一眼目录。重点检查这几个位置assets目录很多黑灰产App会把加密配置、远程下发页面、内置字库藏在这里。lib目录看包含哪些CPU架构的so文件。只放armeabi-v7a说明他们不太在乎兼容性但更常见的是放了arm64-v8a和armeabi-v7a两份。res目录里面是资源文件偶尔能发现明文网址。classes.dex如果这个文件特别小比如只有几KB那基本可以确定这个dex只是个壳真正的代码在so里运行时释放。我习惯用一条命令先看包结构unzip app.apk -d app_source find app_source -type f | sort对体积异常的App再查一下是否用了加固。常见的加固厂商特征会留在lib目录里比如有的会放libDexHelper.so、有的会有libjiagu.so看到这类文件就知道要做脱壳处理不能直接拿jadx反编译。2.2 第二步jadx反编译找到入口和敏感信息jadx是目前最好用的Java层反编译工具之一它能把dex直接还原成接近源码的Java文件。用法很简单jadx -d jadx_out app.apk打开jadx_out后先看AndroidManifest.xml找到android.intent.action.MAIN对应的activity这是入口。再看申请的权限如果出现SEND_SMS、READ_CONTACTS、SYSTEM_ALERT_WINDOW这类高风险权限基本可以判断这个App目的不纯。接下来用jadx的全局搜索功能搜索以下关键词命中率和定位速度极高base64大概率用于编码传输字段。AES/CBC/PKCS5Padding对称加密密钥就在代码或so里。RSA/ECB非对称加密常用于加密关键参数。sign/md5/HmacSHA256签名算法一般用来生成token或请求签名。http/https直接暴露服务器地址。这个样本十分典型我在搜“sign”时直接定位到一个名为 SignUtils 的工具类里面有一个静态方法 getSign(Map params)返回一串32位小写字符串。里面调用了MD5但明显不是单纯拼接参数而是有一个固定前缀字符串参与拼接。这个后缀就是后面协议分析的关键。2.3 第三步识别壳与加固方式决定用不用脱壳如果jadx打开后类全是空的或者只能看到几个壳的入口类说明App加壳了。这时候就要用脱壳工具把真正的dex提取出来。脱壳分两种一是在内存中dump二是主动调用壳的解密逻辑。常用的有FART、frida-dexdump这类方案。我用得最顺手的是frida-dexdump配合Frida在App启动后从内存中扫描Dex内存区域并dump下来再扔给jadx看。操作方式简化为三步frida-ps -U frida-dexdump -U -f com.example.sample -o dump.dexdump出来后把dump.dex再用jadx打开。这个方法对市面大部分加固都有效但要注意有些壳会在启动后释放dex又立刻删除或者只释放需要的类这时候需要抓取时机通常是在登录页加载完成的瞬间dump成功率最高。2.4 第四步分析代码逻辑画出调用关系定位到SignUtils后继续往上找谁调用了它。jadx可以在方法上右键“Find Usage”能快速列出所有调用点。我跟着调用链找到了一个 RequestManager 的类它负责构建HttpURLConnection请求把所有参数用HashMap收集后统一调用 SignUtils.getSign(params)再把签名值塞进请求头。这个Process就是很典型的“本地签名 服务端验签”结构。想要绕过签名要么反向破解算法生成合法签名要么直接Hook这个函数让它返回正确值。静态分析阶段先记下这个入口后面抓包会用到。静态分析还应该做一件事检查是否开启debuggable。在AndroidManifest里如果android:debuggabletrue那后面动态调试就能直接附加进程。但大多数黑样本会关掉它所以后面要用Frida的spawn方式去注入而不是attach。3. 动态调试与Hook让应用自己把秘密说出来静态分析只能告诉你代码长什么样动态调试能告诉你代码运行时到底在干什么。Hook的本质就是在函数入口和出口插桩把参数、返回值和调用栈全部打印出来。3.1 Frida环境搭建Frida是目前最主流的动态插桩框架服务端跑在手机/模拟器上客户端跑在电脑上。安装时注意版本匹配手机上安装 frida-server版本要和电脑端 frida 保持一致。模拟器选x86版本真机选arm64版本。root是必须的没有root的手机寸步难行。我用的环境是Pixel模拟器Android 9启动frida-serveradb push frida-server /data/local/tmp/ adb shell chmod x /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server 电脑端验证连接frida-ps -U能看到进程列表就说明通了。3.2 Hook Java层函数打印参数与返回值现在要Hook SignUtils.getSign方法。先用Objection看一下当前App加载了哪些类和方法Objection是Frida的高层封装一键枚举类objection -g com.example.sample explore android hooking list classes或者直接用Frida脚本干净利落。Hook一个Java方法核心是找到类加载器和方法签名然后用 Java.perform 包裹js脚本。我用的是这样的脚本Java.perform(function() { var SignUtils Java.use(com.example.SignUtils); SignUtils.getSign.overload(java.util.Map).implementation function(params) { var result this.getSign(params); console.log(params: params.toString()); console.log(result: result); return result; }; });保存为 hook_sign.js执行frida -U -f com.example.sample -l hook_sign.js启动后App刚执行到getSign控制台就输出了明文参数和签名结果。这一步直接证实了静态分析的结论签名用的就是MD5加盐拼接。注意很多App会做Frida检测常见检测方式是检查进程是否加载了frida-agent.so、遍历端口27042、执行frida-server等命令特征。遇到这种情况不能硬刚要临时改Frida的轮子或者用gadget模式以注入so的方式绕过这也是为什么分析环境至少需要两台设备一台目标一台调试。3.3 动态定位加解密函数如果App连参数都是加密的静态搜索加解密代码时找不到关键函数那就用Hook的方式暴力筛查。我写过一个自动遍历所有java.lang.String构造函数的脚本凡是new出来的字符串都会打印通过日志回溯可以快速看到明文和密文的地图。但更推荐直接Hook常见的加解密类Java.perform(function() { var Cipher Java.use(javax.crypto.Cipher); Cipher.doFinal.overload([B).implementation function(input) { var result this.doFinal(input); console.log(Cipher input: bytesToHex(input)); console.log(Cipher output: bytesToHex(result)); return result; }; });这个方法能捕获所有经过Cipher的数据就算换算法也能看到中间状态。配合日志的时间戳和抓包数据一一对应很容易定位哪个加密方法对应哪个请求参数。3.4 绕过root检测和反调试涉赌样本基本都会做root检测如果检测到root直接退出或闪退。处理思路很简单先看检测点在哪然后Hook使其返回false。用Objection的自带插件可以快速绕过一部分检测objection -g com.example.sample explore android root disable android sslpinning disable但别忘了样本可能用了多态检测比如多个函数交叉验证root环境。极端情况需要手动分析每个检测方法。我的经验是直接搜“root”相关字符串用jadx找到MainActivity里的isRooted方法然后Hook掉它。Java.perform(function() { var AntiRoot Java.use(com.example.AntiCheck); AntiRoot.isRooted.implementation function() { return false; }; });这个样本还做了反调试运行时会检查自身是否处于被调试状态。绕过方法是在调试模式下Hook android.os.Debug.isDebuggerConnected返回false。4. 协议分析抓包、解密与还原全套通信流程协议分析是整个逆向流程的收尾环节也是最像“侦探”的部分。核心目标是把App发给服务器的请求、服务器返回的响应全部还原成可读的明文然后验证签名逻辑最后能做到自己构造一条合法请求。4.1 HTTPS抓包环境搭建涉赌App百分之九十九都走HTTPS服务器域名五花八门有的甚至用IP直连。你直接在手机上装个抓包软件是抓不到HTTPS明文的因为证书校验那一关就过不去。我常用的组合是Charles抓包 Frida绕过SSL Pinning。Charles从电脑端下载安装后设置代理端口8888手机Wi-Fi代理指向电脑IP。然后给手机安装Charles的根证书。这一套下来绝大多数普通HTTPS流量直接被解密。但涉赌样本通常有SSL Pinning也就是客户端内置了服务器证书指纹一旦发现CA不是它信任的那个就直接断开连接。这时就必须用Frida来Hook证书校验逻辑。最简单的办法是使用objection自带的禁用SSL Pinningobjection -g com.example.sample explore android sslpinning disable这条命令能处理大部分基于OkHttp、HttpURLConnection的校验。如果碰上原生层用socket自己实现的TLS那就得去so里找但复杂度会大很多。4.2 抓包数据对比定位加密字段Charles抓到的请求长这样POST /api/v1/bet HTTP/1.1 Host: 123.45.67.89 Content-Type: application/x-www-form-urlencoded User-Agent: okhttp/3.12.0 Sign: e9669e0cbcc1f40c7e1f9a7f3f2c47aa Content-Length: 156 uid10001matchId99821odds1.85amount100betType1tokene6c3f8b4a0d90e3f51c2b9a4f6d1c7b8注意看Sign这个请求头和uid、matchId这些字段的拼接顺序对应静态分析的SignUtils。再注意token字段是一串32字节hex看起来像AES加密后的结果。我把token抓出来在自己的Python脚本里用AES算法尝试解密如果密钥和IV能从反编译代码中找到直接解出明文。通常token里包含用户的userId、时间戳和一句“合法凭证”的信息服务器就是靠它识别用户身份。如果解密后看到类似“201915231”这样的时间戳基本就能断定这是一个时间戳相关的Token有效期可能只有几分钟。4.3 还原签名算法并实现重放把签名算法扣出来后我习惯写一个Python脚本模拟客户端生成签名然后直接发请求。这一步的意义在于验证自己理解的协议是否完全正确。模拟客户端签名脚本的核心逻辑import hashlib import time def get_sign(params_dict): # 对应App里的MD5拼接逻辑 sorted_keys sorted(params_dict.keys()) raw for k in sorted_keys: raw f{k}{params_dict[k]} raw raw[:-1] raw A1B2C3D4E5F6 # 从反编译拿到的固定盐值 return hashlib.md5(raw.encode()).hexdigest()拿到签名后再用requests库构造一个同样的POST请求发到服务器。如果服务器响应和App内一致那说明协议分析闭环了。实测下来大多数样本的验签就只是算MD5根本没有防重放机制我改一下参数它照样返回合法数据这正是这类App风控薄弱的有力证据。4.4 Native协议分析当so库参与加密时如果加密逻辑不在Java层而在so里抓包看到的是加了密的字节流这时候要把so库拖进IDA Pro定位到加密函数。但这里有个轻量级做法先用Frida HookSystem.loadLibrary拿到加载的so路径再Hook so里导出的关键函数。看so的导出表readelf -s libnative-lib.so | grep -i encrypt或者在IDA里搜字符串连续十几个字节的ASCII可读字符往往是提示密钥或IV。有一回样本的so里直接写死了“ThisIsSecretKey12345”虽然加了各种混淆但字符串暴露了一切。另一个常见手段是Hook JNI接口。Java层调用native层的方法都会有jstring或jbyteArray参数Frida可以拦到这些参数。写Frida脚本时用Java.use定位native方法然后对它做替换一样可以打印参数内容。Native层不等于绝对安全JNI只是隔离层不是加密盾。5. 常见问题与排查技巧在分析上面这个样本的过程中我几次卡住也整理了一些通用于同类项目的排查技巧。5.1 脱壳后jadx仍然看不到代码怎么办有些壳会把dex转成自定义格式存放在assets里运行时才解密。frida-dexdump如果dump时机不对dump出来的dex不完整。解决办法用Frida脚本主动搜索内存里的dex魔数“dex\n035\0”或“dex\n037\0”。对多Dex的应用启动后延迟几秒再dump。用FART的主动调用脱壳模式能够从内存中修复函数体对某些混淆程度极高的样本效果更好。5.2 抓包只有CONNECT请求没有HTTP明文这是最经典的HTTPS抓包失败场景。原因一般是代理没有设置成功或者SSL Pinning检测拦截或者App本身用了非标准端口比如443之外的自定义端口。处理顺序确认Charles里“SSL Proxying”勾选了全部host和port。用objection关闭SSL Pinning。如果App做了代理检测需要配合Frida Hook System.getProperty(http.proxyHost)让App认为没有代理。5.3 Frida频繁掉线或注入失败多半是Android版本和frida版本不兼容。Android 10以上对ptrace限制严格Frida需要配合zygote模式运行。还有一点如果在真机上跑厂商的定制系统可能屏蔽了Frida用的调试端口。模拟器反而是更稳定的环境。如果遇到“unable to inject”错误在启动命令前加GUM_INJECT_WAIT环境变量有时能解决时序问题GUM_INJECT_WAIT300 frida -U -f com.example.sample -l hook_sign.js5.4 签名算法无法复现这类App的签名算法往往不在单一函数里而是先拼接、再Base64、再MD5甚至中间还编码了固定字符。建议用Frida把getSign方法的params参数打印出来后在Python里逐一尝试拼接顺序。最笨但最有效的方法是打印出最终签名后对着拼接字符串的几种可能样式逆向推算哪些字段参与了计算。最快路径是直接Hook签名函数用它的返回值反推。比如先构造 “a1b2c3key”如果算出来的MD5和App一致说明拼接顺序对了key对了。5.5 隔离环境与安全提醒最后必须再次强调分析这类样本一定要在隔离环境里做不要在自己主力机或日常设备上运行。我一般用单独的模拟器开启“仅限安全模式”关闭自动同步和通讯录权限并且把模拟器快照保存一份防止被恶意样本破坏。分析过程中产生的apk、dump文件、日志全部放在加密目录里不随意流转。6. 实操心得入门逆向最有效的姿势这条路走下来最大的体会就是逆向不是一门孤立的技术而是“看得懂代码、猜得到行为、模拟得了通信”三件事的组合。你从静态分析里看到SignUtils的MD5从动态调试里抓到真实参数从抓包里看到实际请求三个环节互相验证才算是真正吃透了一个样本。建议新手按这个顺序练习先拿自己做的小Demo写一个带登录和接口请求的App用Charles抓自己App的包然后尝试修改请求参数让服务器报错。等熟悉抓包和jadx之后再找一个带加固的开源恶意样本分析上手Frida Hook。整个过程不需要背任何源码理解“数据怎么流动、校验在哪发生、变量怎么传递”才是关键。如果你刚好有接入合规风控项目的机会比如分析疑似违规的竞猜类App并提取特征用于拦截那是再好不过的练手场景。这类App的代码质量通常不高、安全防护水平参差正好适合新手一步步拆解。但边界要清晰不做技术变现的边缘操作不提供帮助绕过监管的方案只在合法授权范围内做安全分析。最后分享一个小技巧分析过程中养成随手记笔记的习惯把每个类的关键方法、每个请求的参数、每段加密字符串的来源都记录下来。这个笔记是你最后写报告和复盘的核心素材也是你从“能跑通脚本”进阶到“能讲清原理”的重要跳板。