第三十课:综合实战案例——一个完整App的逆向分析流程

终于到了最后一课。说实话,前面二十九课讲了很多技术点,但都是零散的。今天咱们把整个流程串起来——从拿到一个APK开始,到最终还原出它的通信协议。我管这叫“一条龙逆向”。

你可能会问:“学了这么多,到底怎么用?”嗯,这节课就是答案。我会用一个虚构但真实的案例,带你走一遍完整的逆向流程。这个案例我去年在某个项目中遇到过类似的,当时折腾了三天才搞定。

案例背景:一个“天气助手”App

假设我们拿到一个叫“天气助手”的App。它功能很简单:显示天气、推送预警。但奇怪的是,它会在后台频繁联网,而且流量不小。我们怀疑它有“小动作”。

目标很明确:搞清楚它到底在传什么数据,协议是什么样的

整个流程分四步走:

  1. 静态分析——先看代码,摸清结构
  2. 动态调试——跑起来,看它怎么动
  3. 协议还原——把通信内容拼出来
  4. 验证与总结——确认我们没搞错

核心思路:静态分析找线索,动态调试抓证据,协议还原出真相。三步缺一不可。

① 静态分析 反编译、查字符串 找关键类与方法 ② 动态调试 Hook关键函数 抓网络包 ③ 协议还原 解析数据格式 还原加密逻辑 ④ 验证总结 重放测试 编写报告 迭代分析

第一步:静态分析——先摸清底细

拿到APK,我习惯先扔进 jadx 反编译。别急着看代码,先看 AndroidManifest.xml。这里藏着很多信息。

我注意到几个关键点:

  • 权限列表里有 INTERNETACCESS_NETWORK_STATE,这正常。
  • 但还有一个 READ_PHONE_STATE——一个天气App要这个干嘛?
  • 注册了一个 WeatherService 后台服务,还有 AlarmReceiver 定时广播。

嗯,有猫腻。我顺着 WeatherService 点进去看代码。

核心代码大概长这样:

public class WeatherService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 获取设备信息
        String imei = getImei(this);
        String mac = getMacAddress(this);
        // 组装数据
        JSONObject data = new JSONObject();
        data.put("imei", imei);
        data.put("mac", mac);
        data.put("timestamp", System.currentTimeMillis());
        // 加密
        String encrypted = encrypt(data.toString(), "secret_key_2024");
        // 发送
        sendToServer(encrypted);
        return START_STICKY;
    }
}

看到了吧?它把 IMEI 和 MAC 地址收集起来,加密后发到服务器。这哪是天气App,分明是个数据采集器。

我的经验:静态分析时,重点关注 ServiceBroadcastReceiverContentProvider 这些组件。它们往往是“干坏事”的入口。另外,字符串搜索也很关键——搜 httpapisecretencrypt 这些关键词,能快速定位敏感逻辑。

第二步:动态调试——让它现原形

静态分析只能看到“它想做什么”,但看不到“它实际做了什么”。这时候就需要动态调试了。

我用了两个工具:

  • Frida——Hook 关键函数,看参数和返回值
  • Charles——抓网络包,看它发了什么

先写个 Frida 脚本,Hook 那个 encrypt 方法:

Java.perform(function() {
    var WeatherService = Java.use("com.weather.WeatherService");
    WeatherService.encrypt.implementation = function(data, key) {
        console.log("[*] encrypt called");
        console.log("    data: " + data);
        console.log("    key: " + key);
        var result = this.encrypt(data, key);
        console.log("    result: " + result);
        return result;
    };
});

跑起来后,控制台输出:

[*] encrypt called
    data: {"imei":"356938035643803","mac":"02:00:00:00:00:00","timestamp":1700000000000}
    key: secret_key_2024
    result: U2FsdGVkX1+9v8Vz5L7p3Q==...

同时,Charles 抓到一条 POST 请求:

POST /api/collect HTTP/1.1
Host: data.weather-app.com
Content-Type: text/plain

U2FsdGVkX1+9v8Vz5L7p3Q==...

数据对上了。加密后的内容直接发到了 /api/collect 接口。

注意:动态调试时,记得把手机代理设置到 Charles。如果 App 做了 SSL Pinning,你得先用 Frida 绕过它。我之前遇到过一款 App,用了 OkHttp 的证书锁定,折腾了半天才绕过去。具体方法前面课程讲过,这里不重复了。

第三步:协议还原——把加密扒干净

现在我们知道它用了 AES 加密(从 encrypt 方法的实现里看出来的),密钥是 secret_key_2024。但光知道这些还不够——我们要还原出完整的协议格式。

我继续分析 encrypt 方法的内部逻辑。jadx 里看到:

private String encrypt(String data, String key) {
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec("1234567890abcdef".getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        return Base64.encodeToString(encrypted, Base64.DEFAULT);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

好,参数全了:

  • 算法:AES/CBC/PKCS5Padding
  • 密钥:secret_key_2024
  • IV:1234567890abcdef
  • 输出:Base64 编码

我写个 Python 脚本验证一下:

from Crypto.Cipher import AES
import base64

key = b'secret_key_2024'
iv = b'1234567890abcdef'
cipher = AES.new(key, AES.MODE_CBC, iv)

# 解密抓到的数据
encrypted_data = base64.b64decode("U2FsdGVkX1+9v8Vz5L7p3Q==...")
decrypted = cipher.decrypt(encrypted_data)
print(decrypted.decode('utf-8').strip())
# 输出:{"imei":"356938035643803","mac":"02:00:00:00:00:00","timestamp":1700000000000}

完美还原。协议格式就是:JSON 数据 → AES 加密 → Base64 编码 → POST 发送

协议还原总结

字段
请求方式POST
URL/api/collect
Content-Typetext/plain
加密算法AES/CBC/PKCS5Padding
密钥secret_key_2024
IV1234567890abcdef
编码Base64
数据格式JSON

第四步:验证与总结——确认没搞错

还原出协议后,我做了两件事:

  1. 重放测试:用 Python 构造同样的请求,发到服务器。服务器返回了 200 OK,说明协议还原正确。
  2. 修改数据测试:把 IMEI 改成假的,再发一次。服务器也接受了——说明它没有做数据校验。

到这里,整个逆向分析就完成了。我们不仅搞清楚了 App 的“小动作”,还完整还原了它的通信协议。

一点心得:逆向分析不是一次性的。很多时候你需要反复在静态和动态之间切换。比如我一开始静态分析没找到密钥,后来动态 Hook 才拿到。所以别怕折腾,多试几次就熟了。

写在最后

这个案例虽然简单,但流程是通用的。你以后遇到任何 App,都可以按这个套路来:

  • 先静态分析,摸清结构
  • 再动态调试,抓取证据
  • 最后协议还原,彻底搞懂

记住,工具只是手段,思路才是核心。遇到问题多问自己:“它为什么要这么做?”“这里能不能 Hook?”“这个加密能不能绕过?”

好了,课程到这里就结束了。希望这三十节课能帮你打开逆向工程的大门。以后在实战中遇到问题,欢迎随时交流。


公众号:蓝海资料掘金营,微信deep3321